diff --git a/src/ImageSharp/Compression/Zlib/Adler32.cs b/src/ImageSharp/Compression/Zlib/Adler32.cs
deleted file mode 100644
index 6f2185081..000000000
--- a/src/ImageSharp/Compression/Zlib/Adler32.cs
+++ /dev/null
@@ -1,435 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Runtime.Intrinsics;
-using System.Runtime.Intrinsics.Arm;
-using System.Runtime.Intrinsics.X86;
-
-#pragma warning disable IDE0007 // Use implicit type
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// Calculates the 32 bit Adler checksum of a given buffer according to
-/// RFC 1950. ZLIB Compressed Data Format Specification version 3.3)
-///
-internal static class Adler32
-{
- ///
- /// The default initial seed value of a Adler32 checksum calculation.
- ///
- public const uint SeedValue = 1U;
-
- // Largest prime smaller than 65536
- private const uint BASE = 65521;
-
- // NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1
- private const uint NMAX = 5552;
-
- private const int MinBufferSize = 64;
-
- private const int BlockSize = 1 << 5;
-
- // The C# compiler emits this as a compile-time constant embedded in the PE file.
- private static ReadOnlySpan Tap1Tap2 =>
- [
- 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, // tap1
- 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 // tap2
- ];
-
- ///
- /// Calculates the Adler32 checksum with the bytes taken from the span.
- ///
- /// The readonly span of bytes.
- /// The .
- [MethodImpl(InliningOptions.ShortMethod)]
- public static uint Calculate(ReadOnlySpan buffer)
- => Calculate(SeedValue, buffer);
-
- ///
- /// Calculates the Adler32 checksum with the bytes taken from the span and seed.
- ///
- /// The input Adler32 value.
- /// The readonly span of bytes.
- /// The .
- [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
- public static uint Calculate(uint adler, ReadOnlySpan buffer)
- {
- if (buffer.IsEmpty)
- {
- return adler;
- }
-
- if (Avx2.IsSupported && buffer.Length >= MinBufferSize)
- {
- return CalculateAvx2(adler, buffer);
- }
-
- if (Ssse3.IsSupported && buffer.Length >= MinBufferSize)
- {
- return CalculateSse(adler, buffer);
- }
-
- if (AdvSimd.IsSupported)
- {
- return CalculateArm(adler, buffer);
- }
-
- return CalculateScalar(adler, buffer);
- }
-
- // Based on https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
- [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
- private static unsafe uint CalculateSse(uint adler, ReadOnlySpan buffer)
- {
- uint s1 = adler & 0xFFFF;
- uint s2 = (adler >> 16) & 0xFFFF;
-
- // Process the data in blocks.
- uint length = (uint)buffer.Length;
- uint blocks = length / BlockSize;
- length -= blocks * BlockSize;
-
- fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
- {
- fixed (byte* tapPtr = &MemoryMarshal.GetReference(Tap1Tap2))
- {
- byte* localBufferPtr = bufferPtr;
-
- // _mm_setr_epi8 on x86
- Vector128 tap1 = Sse2.LoadVector128((sbyte*)tapPtr);
- Vector128 tap2 = Sse2.LoadVector128((sbyte*)(tapPtr + 0x10));
- Vector128 zero = Vector128.Zero;
- Vector128 ones = Vector128.Create((short)1);
-
- while (blocks > 0)
- {
- uint n = NMAX / BlockSize; /* The NMAX constraint. */
- if (n > blocks)
- {
- n = blocks;
- }
-
- blocks -= n;
-
- // Process n blocks of data. At most NMAX data bytes can be
- // processed before s2 must be reduced modulo BASE.
- Vector128 v_ps = Vector128.CreateScalar(s1 * n);
- Vector128 v_s2 = Vector128.CreateScalar(s2);
- Vector128 v_s1 = Vector128.Zero;
-
- do
- {
- // Load 32 input bytes.
- Vector128 bytes1 = Sse3.LoadDquVector128(localBufferPtr);
- Vector128 bytes2 = Sse3.LoadDquVector128(localBufferPtr + 0x10);
-
- // Add previous block byte sum to v_ps.
- v_ps = Sse2.Add(v_ps, v_s1);
-
- // Horizontally add the bytes for s1, multiply-adds the
- // bytes by [ 32, 31, 30, ... ] for s2.
- v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes1, zero).AsUInt32());
- Vector128 mad1 = Ssse3.MultiplyAddAdjacent(bytes1, tap1);
- v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad1, ones).AsUInt32());
-
- v_s1 = Sse2.Add(v_s1, Sse2.SumAbsoluteDifferences(bytes2, zero).AsUInt32());
- Vector128 mad2 = Ssse3.MultiplyAddAdjacent(bytes2, tap2);
- v_s2 = Sse2.Add(v_s2, Sse2.MultiplyAddAdjacent(mad2, ones).AsUInt32());
-
- localBufferPtr += BlockSize;
- }
- while (--n > 0);
-
- v_s2 = Sse2.Add(v_s2, Sse2.ShiftLeftLogical(v_ps, 5));
-
- // Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
- const byte s2301 = 0b1011_0001; // A B C D -> B A D C
- const byte s1032 = 0b0100_1110; // A B C D -> C D A B
-
- v_s1 = Sse2.Add(v_s1, Sse2.Shuffle(v_s1, s1032));
-
- s1 += v_s1.ToScalar();
-
- v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s2301));
- v_s2 = Sse2.Add(v_s2, Sse2.Shuffle(v_s2, s1032));
-
- s2 = v_s2.ToScalar();
-
- // Reduce.
- s1 %= BASE;
- s2 %= BASE;
- }
-
- if (length > 0)
- {
- HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
- }
-
- return s1 | (s2 << 16);
- }
- }
- }
-
- // Based on: https://github.com/zlib-ng/zlib-ng/blob/develop/arch/x86/adler32_avx2.c
- [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
- public static unsafe uint CalculateAvx2(uint adler, ReadOnlySpan buffer)
- {
- uint s1 = adler & 0xFFFF;
- uint s2 = (adler >> 16) & 0xFFFF;
- uint length = (uint)buffer.Length;
-
- fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
- {
- byte* localBufferPtr = bufferPtr;
-
- Vector256 zero = Vector256.Zero;
- Vector256 dot3v = Vector256.Create((short)1);
- Vector256 dot2v = Vector256.Create(32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1);
-
- // Process n blocks of data. At most NMAX data bytes can be
- // processed before s2 must be reduced modulo BASE.
- Vector256 vs1 = Vector256.CreateScalar(s1);
- Vector256 vs2 = Vector256.CreateScalar(s2);
-
- while (length >= 32)
- {
- int k = length < NMAX ? (int)length : (int)NMAX;
- k -= k % 32;
- length -= (uint)k;
-
- Vector256 vs10 = vs1;
- Vector256 vs3 = Vector256.Zero;
-
- while (k >= 32)
- {
- // Load 32 input bytes.
- Vector256 block = Avx.LoadVector256(localBufferPtr);
-
- // Sum of abs diff, resulting in 2 x int32's
- Vector256 vs1sad = Avx2.SumAbsoluteDifferences(block, zero);
-
- vs1 = Avx2.Add(vs1, vs1sad.AsUInt32());
- vs3 = Avx2.Add(vs3, vs10);
-
- // sum 32 uint8s to 16 shorts.
- Vector256 vshortsum2 = Avx2.MultiplyAddAdjacent(block, dot2v);
-
- // sum 16 shorts to 8 uint32s.
- Vector256 vsum2 = Avx2.MultiplyAddAdjacent(vshortsum2, dot3v);
-
- vs2 = Avx2.Add(vsum2.AsUInt32(), vs2);
- vs10 = vs1;
-
- localBufferPtr += BlockSize;
- k -= 32;
- }
-
- // Defer the multiplication with 32 to outside of the loop.
- vs3 = Avx2.ShiftLeftLogical(vs3, 5);
- vs2 = Avx2.Add(vs2, vs3);
-
- s1 = (uint)Numerics.EvenReduceSum(vs1.AsInt32());
- s2 = (uint)Numerics.ReduceSum(vs2.AsInt32());
-
- s1 %= BASE;
- s2 %= BASE;
-
- vs1 = Vector256.CreateScalar(s1);
- vs2 = Vector256.CreateScalar(s2);
- }
-
- if (length > 0)
- {
- HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
- }
-
- return s1 | (s2 << 16);
- }
- }
-
- // Based on: https://github.com/chromium/chromium/blob/master/third_party/zlib/adler32_simd.c
- [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
- private static unsafe uint CalculateArm(uint adler, ReadOnlySpan buffer)
- {
- // Split Adler-32 into component sums.
- uint s1 = adler & 0xFFFF;
- uint s2 = (adler >> 16) & 0xFFFF;
- uint length = (uint)buffer.Length;
-
- // Process the data in blocks.
- long blocks = length / BlockSize;
- length -= (uint)(blocks * BlockSize);
- fixed (byte* bufferPtr = &MemoryMarshal.GetReference(buffer))
- {
- byte* localBufferPtr = bufferPtr;
-
- while (blocks != 0)
- {
- uint n = NMAX / BlockSize;
- if (n > blocks)
- {
- n = (uint)blocks;
- }
-
- blocks -= n;
-
- // Process n blocks of data. At most nMax data bytes can be
- // processed before s2 must be reduced modulo Base.
- Vector128 vs1 = Vector128.Zero;
- Vector128 vs2 = vs1.WithElement(3, s1 * n);
- Vector128 vColumnSum1 = Vector128.Zero;
- Vector128 vColumnSum2 = Vector128.Zero;
- Vector128 vColumnSum3 = Vector128.Zero;
- Vector128 vColumnSum4 = Vector128.Zero;
-
- do
- {
- // Load 32 input bytes.
- Vector128 bytes1 = AdvSimd.LoadVector128(localBufferPtr).AsUInt16();
- Vector128 bytes2 = AdvSimd.LoadVector128(localBufferPtr + 0x10).AsUInt16();
-
- // Add previous block byte sum to v_s2.
- vs2 = AdvSimd.Add(vs2, vs1);
-
- // Horizontally add the bytes for s1.
- vs1 = AdvSimd.AddPairwiseWideningAndAdd(
- vs1.AsUInt32(),
- AdvSimd.AddPairwiseWideningAndAdd(AdvSimd.AddPairwiseWidening(bytes1.AsByte()).AsUInt16(), bytes2.AsByte()));
-
- // Vertically add the bytes for s2.
- vColumnSum1 = AdvSimd.AddWideningLower(vColumnSum1, bytes1.GetLower().AsByte());
- vColumnSum2 = AdvSimd.AddWideningLower(vColumnSum2, bytes1.GetUpper().AsByte());
- vColumnSum3 = AdvSimd.AddWideningLower(vColumnSum3, bytes2.GetLower().AsByte());
- vColumnSum4 = AdvSimd.AddWideningLower(vColumnSum4, bytes2.GetUpper().AsByte());
-
- localBufferPtr += BlockSize;
- }
- while (--n > 0);
-
- vs2 = AdvSimd.ShiftLeftLogical(vs2, 5);
-
- // Multiply-add bytes by [ 32, 31, 30, ... ] for s2.
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetLower(), Vector64.Create((ushort)32, 31, 30, 29));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum1.GetUpper(), Vector64.Create((ushort)28, 27, 26, 25));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetLower(), Vector64.Create((ushort)24, 23, 22, 21));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum2.GetUpper(), Vector64.Create((ushort)20, 19, 18, 17));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetLower(), Vector64.Create((ushort)16, 15, 14, 13));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum3.GetUpper(), Vector64.Create((ushort)12, 11, 10, 9));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetLower(), Vector64.Create((ushort)8, 7, 6, 5));
- vs2 = AdvSimd.MultiplyWideningLowerAndAdd(vs2, vColumnSum4.GetUpper(), Vector64.Create((ushort)4, 3, 2, 1));
-
- // Sum epi32 ints v_s1(s2) and accumulate in s1(s2).
- Vector64 sum1 = AdvSimd.AddPairwise(vs1.GetLower(), vs1.GetUpper());
- Vector64 sum2 = AdvSimd.AddPairwise(vs2.GetLower(), vs2.GetUpper());
- Vector64 s1s2 = AdvSimd.AddPairwise(sum1, sum2);
-
- // Store the results.
- s1 += AdvSimd.Extract(s1s2, 0);
- s2 += AdvSimd.Extract(s1s2, 1);
-
- // Reduce.
- s1 %= BASE;
- s2 %= BASE;
- }
-
- if (length > 0)
- {
- HandleLeftOver(localBufferPtr, length, ref s1, ref s2);
- }
-
- return s1 | (s2 << 16);
- }
- }
-
- private static unsafe void HandleLeftOver(byte* localBufferPtr, uint length, ref uint s1, ref uint s2)
- {
- if (length >= 16)
- {
- s2 += s1 += localBufferPtr[0];
- s2 += s1 += localBufferPtr[1];
- s2 += s1 += localBufferPtr[2];
- s2 += s1 += localBufferPtr[3];
- s2 += s1 += localBufferPtr[4];
- s2 += s1 += localBufferPtr[5];
- s2 += s1 += localBufferPtr[6];
- s2 += s1 += localBufferPtr[7];
- s2 += s1 += localBufferPtr[8];
- s2 += s1 += localBufferPtr[9];
- s2 += s1 += localBufferPtr[10];
- s2 += s1 += localBufferPtr[11];
- s2 += s1 += localBufferPtr[12];
- s2 += s1 += localBufferPtr[13];
- s2 += s1 += localBufferPtr[14];
- s2 += s1 += localBufferPtr[15];
-
- localBufferPtr += 16;
- length -= 16;
- }
-
- while (length-- > 0)
- {
- s2 += s1 += *localBufferPtr++;
- }
-
- if (s1 >= BASE)
- {
- s1 -= BASE;
- }
-
- s2 %= BASE;
- }
-
- [MethodImpl(InliningOptions.HotPath | InliningOptions.ShortMethod)]
- private static unsafe uint CalculateScalar(uint adler, ReadOnlySpan buffer)
- {
- uint s1 = adler & 0xFFFF;
- uint s2 = (adler >> 16) & 0xFFFF;
-
- fixed (byte* bufferPtr = buffer)
- {
- byte* localBufferPtr = bufferPtr;
- uint length = (uint)buffer.Length;
-
- while (length > 0)
- {
- uint k = length < NMAX ? length : NMAX;
- length -= k;
-
- while (k >= 16)
- {
- s2 += s1 += localBufferPtr[0];
- s2 += s1 += localBufferPtr[1];
- s2 += s1 += localBufferPtr[2];
- s2 += s1 += localBufferPtr[3];
- s2 += s1 += localBufferPtr[4];
- s2 += s1 += localBufferPtr[5];
- s2 += s1 += localBufferPtr[6];
- s2 += s1 += localBufferPtr[7];
- s2 += s1 += localBufferPtr[8];
- s2 += s1 += localBufferPtr[9];
- s2 += s1 += localBufferPtr[10];
- s2 += s1 += localBufferPtr[11];
- s2 += s1 += localBufferPtr[12];
- s2 += s1 += localBufferPtr[13];
- s2 += s1 += localBufferPtr[14];
- s2 += s1 += localBufferPtr[15];
-
- localBufferPtr += 16;
- k -= 16;
- }
-
- while (k-- > 0)
- {
- s2 += s1 += *localBufferPtr++;
- }
-
- s1 %= BASE;
- s2 %= BASE;
- }
-
- return (s2 << 16) | s1;
- }
- }
-}
diff --git a/src/ImageSharp/Compression/Zlib/ChunkedWriteStream.cs b/src/ImageSharp/Compression/Zlib/ChunkedWriteStream.cs
new file mode 100644
index 000000000..44f655011
--- /dev/null
+++ b/src/ImageSharp/Compression/Zlib/ChunkedWriteStream.cs
@@ -0,0 +1,143 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using System.Buffers;
+using SixLabors.ImageSharp.Memory;
+
+namespace SixLabors.ImageSharp.Compression.Zlib;
+
+///
+/// A write-only stream that groups written bytes into fixed-length segments. Bytes are
+/// collected in a pooled segment buffer; when the buffer is full the supplied delegate is
+/// invoked with the completed segment and the buffer is reused. The final partial segment,
+/// if any, is emitted on disposal. The delegate owns the destination; this stream writes
+/// nowhere itself and is the write-side counterpart of .
+///
+internal sealed class ChunkedWriteStream : Stream
+{
+ ///
+ /// The segment length used when the caller does not require a specific framing size.
+ ///
+ public const int DefaultSegmentLength = 64 * 1024;
+
+ private readonly IMemoryOwner segmentOwner;
+ private readonly Memory segment;
+ private readonly Action> writeSegment;
+ private int segmentFilled;
+ private bool isDisposed;
+
+ ///
+ /// Initializes a new instance of the class using .
+ ///
+ /// The memory allocator used to rent the segment buffer.
+ /// Invoked with each completed segment, and with the final partial segment on disposal.
+ public ChunkedWriteStream(MemoryAllocator allocator, Action> writeSegment)
+ : this(allocator, DefaultSegmentLength, writeSegment)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The memory allocator used to rent the segment buffer.
+ /// The length of each completed segment.
+ /// Invoked with each completed segment, and with the final partial segment on disposal.
+ public ChunkedWriteStream(MemoryAllocator allocator, int segmentLength, Action> writeSegment)
+ {
+ this.segmentOwner = allocator.Allocate(segmentLength);
+ this.segment = this.segmentOwner.Memory;
+ this.writeSegment = writeSegment;
+ }
+
+ ///
+ public override bool CanRead => false;
+
+ ///
+ public override bool CanSeek => false;
+
+ ///
+ public override bool CanWrite => true;
+
+ ///
+ public override long Length => throw new NotSupportedException();
+
+ ///
+ public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
+
+ ///
+ /// Does nothing. A segment is emitted only when it is full or on disposal, so the segment
+ /// length stays fixed however often the producer flushes.
+ ///
+ public override void Flush()
+ {
+ }
+
+ ///
+ public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
+
+ ///
+ public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
+
+ ///
+ public override void SetLength(long value) => throw new NotSupportedException();
+
+ ///
+ public override void WriteByte(byte value)
+ {
+ this.segment.Span[this.segmentFilled++] = value;
+ this.EmitIfFull();
+ }
+
+ ///
+ public override void Write(byte[] buffer, int offset, int count) => this.Write(buffer.AsSpan(offset, count));
+
+ ///
+ public override void Write(ReadOnlySpan buffer)
+ {
+ Span segment = this.segment.Span;
+ while (!buffer.IsEmpty)
+ {
+ int count = Math.Min(segment.Length - this.segmentFilled, buffer.Length);
+ buffer[..count].CopyTo(segment[this.segmentFilled..]);
+ this.segmentFilled += count;
+ buffer = buffer[count..];
+ this.EmitIfFull();
+ }
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (this.isDisposed)
+ {
+ return;
+ }
+
+ this.isDisposed = true;
+ if (disposing)
+ {
+ // The producer has finished, so the partial segment is the final one.
+ if (this.segmentFilled > 0)
+ {
+ this.writeSegment(this.segment.Span[..this.segmentFilled]);
+ this.segmentFilled = 0;
+ }
+
+ this.segmentOwner.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ ///
+ /// Emits the segment buffer when it is full and resets it for reuse.
+ ///
+ private void EmitIfFull()
+ {
+ if (this.segmentFilled == this.segment.Length)
+ {
+ this.writeSegment(this.segment.Span);
+ this.segmentFilled = 0;
+ }
+ }
+}
diff --git a/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs b/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
deleted file mode 100644
index 30761328f..000000000
--- a/src/ImageSharp/Compression/Zlib/DeflateThrowHelper.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Diagnostics.CodeAnalysis;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-internal static class DeflateThrowHelper
-{
- [DoesNotReturn]
- public static void ThrowAlreadyFinished() => throw new InvalidOperationException("Finish() already called.");
-
- [DoesNotReturn]
- public static void ThrowAlreadyClosed() => throw new InvalidOperationException("Deflator already closed.");
-
- [DoesNotReturn]
- public static void ThrowUnknownCompression() => throw new InvalidOperationException("Unknown compression function.");
-
- [DoesNotReturn]
- public static void ThrowNotProcessed() => throw new InvalidOperationException("Old input was not completely processed.");
-
- [DoesNotReturn]
- public static void ThrowNull(string name) => throw new ArgumentNullException(name);
-
- [DoesNotReturn]
- public static void ThrowOutOfRange(string name) => throw new ArgumentOutOfRangeException(name);
-
- [DoesNotReturn]
- public static void ThrowHeapViolated() => throw new InvalidOperationException("Huffman heap invariant violated.");
-
- [DoesNotReturn]
- public static void ThrowNoDeflate() => throw new ImageFormatException("Cannot deflate all input.");
-}
diff --git a/src/ImageSharp/Compression/Zlib/Deflater.cs b/src/ImageSharp/Compression/Zlib/Deflater.cs
deleted file mode 100644
index f642ec85a..000000000
--- a/src/ImageSharp/Compression/Zlib/Deflater.cs
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// This class compresses input with the deflate algorithm described in RFC 1951.
-/// It has several compression levels and three different strategies described below.
-///
-internal sealed class Deflater : IDisposable
-{
- ///
- /// The best and slowest compression level. This tries to find very
- /// long and distant string repetitions.
- ///
- public const int BestCompression = 9;
-
- ///
- /// The worst but fastest compression level.
- ///
- public const int BestSpeed = 1;
-
- ///
- /// The default compression level.
- ///
- public const int DefaultCompression = -1;
-
- ///
- /// This level won't compress at all but output uncompressed blocks.
- ///
- public const int NoCompression = 0;
-
- ///
- /// The compression method. This is the only method supported so far.
- /// There is no need to use this constant at all.
- ///
- public const int Deflated = 8;
-
- ///
- /// Compression level.
- ///
- private int level;
-
- ///
- /// The current state.
- ///
- private int state;
-
- private DeflaterEngine engine;
- private bool isDisposed;
-
- private const int IsFlushing = 0x04;
- private const int IsFinishing = 0x08;
- private const int BusyState = 0x10;
- private const int FlushingState = 0x14;
- private const int FinishingState = 0x1c;
- private const int FinishedState = 0x1e;
- private const int ClosedState = 0x7f;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- /// The compression level, a value between NoCompression and BestCompression.
- ///
- /// if level is out of range.
- public Deflater(MemoryAllocator memoryAllocator, int level)
- {
- if (level == DefaultCompression)
- {
- level = 6;
- }
- else if (level < NoCompression || level > BestCompression)
- {
- throw new ArgumentOutOfRangeException(nameof(level));
- }
-
- // TODO: Possibly provide DeflateStrategy as an option.
- this.engine = new DeflaterEngine(memoryAllocator, DeflateStrategy.Default);
-
- this.SetLevel(level);
- this.Reset();
- }
-
- ///
- /// Compression Level as an enum for safer use
- ///
- public enum CompressionLevel
- {
- ///
- /// The best and slowest compression level. This tries to find very
- /// long and distant string repetitions.
- ///
- BestCompression = Deflater.BestCompression,
-
- ///
- /// The worst but fastest compression level.
- ///
- BestSpeed = Deflater.BestSpeed,
-
- ///
- /// The default compression level.
- ///
- DefaultCompression = Deflater.DefaultCompression,
-
- ///
- /// This level won't compress at all but output uncompressed blocks.
- ///
- NoCompression = Deflater.NoCompression,
-
- ///
- /// The compression method. This is the only method supported so far.
- /// There is no need to use this constant at all.
- ///
- Deflated = Deflater.Deflated
- }
-
- ///
- /// Gets a value indicating whetherthe stream was finished and no more output bytes
- /// are available.
- ///
- public bool IsFinished => (this.state == FinishedState) && this.engine.Pending.IsFlushed;
-
- ///
- /// Gets a value indicating whether the input buffer is empty.
- /// You should then call setInput().
- /// NOTE: This method can also return true when the stream
- /// was finished.
- ///
- public bool IsNeedingInput => this.engine.NeedsInput();
-
- ///
- /// Resets the deflater. The deflater acts afterwards as if it was
- /// just created with the same compression level and strategy as it
- /// had before.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Reset()
- {
- this.state = BusyState;
- this.engine.Pending.Reset();
- this.engine.Reset();
- }
-
- ///
- /// Flushes the current input block. Further calls to Deflate() will
- /// produce enough output to inflate everything in the current input
- /// block. It is used by DeflaterOutputStream to implement Flush().
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Flush() => this.state |= IsFlushing;
-
- ///
- /// Finishes the deflater with the current input block. It is an error
- /// to give more input after this method was called. This method must
- /// be called to force all bytes to be flushed.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Finish() => this.state |= IsFlushing | IsFinishing;
-
- ///
- /// Sets the data which should be compressed next. This should be
- /// only called when needsInput indicates that more input is needed.
- /// The given byte array should not be changed, before needsInput() returns
- /// true again.
- ///
- /// The buffer containing the input data.
- /// The start of the data.
- /// The number of data bytes of input.
- ///
- /// if the buffer was finished or if previous input is still pending.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void SetInput(byte[] input, int offset, int count)
- {
- if ((this.state & IsFinishing) != 0)
- {
- DeflateThrowHelper.ThrowAlreadyFinished();
- }
-
- this.engine.SetInput(input, offset, count);
- }
-
- ///
- /// Sets the compression level. There is no guarantee of the exact
- /// position of the change, but if you call this when needsInput is
- /// true the change of compression level will occur somewhere near
- /// before the end of the so far given input.
- ///
- ///
- /// the new compression level.
- ///
- public void SetLevel(int level)
- {
- if (level == DefaultCompression)
- {
- level = 6;
- }
- else if (level < NoCompression || level > BestCompression)
- {
- throw new ArgumentOutOfRangeException(nameof(level));
- }
-
- if (this.level != level)
- {
- this.level = level;
- this.engine.SetLevel(level);
- }
- }
-
- ///
- /// Deflates the current input block to the given array.
- ///
- /// Buffer to store the compressed data.
- /// Offset into the output array.
- /// The maximum number of bytes that may be stored.
- ///
- /// The number of compressed bytes added to the output, or 0 if either
- /// or returns true or length is zero.
- ///
- public int Deflate(Span output, int offset, int length)
- {
- int origLength = length;
-
- if (this.state == ClosedState)
- {
- DeflateThrowHelper.ThrowAlreadyClosed();
- }
-
- while (true)
- {
- int count = this.engine.Pending.Flush(output, offset, length);
- offset += count;
- length -= count;
-
- if (length == 0 || this.state == FinishedState)
- {
- break;
- }
-
- if (!this.engine.Deflate((this.state & IsFlushing) != 0, (this.state & IsFinishing) != 0))
- {
- switch (this.state)
- {
- case BusyState:
- // We need more input now
- return origLength - length;
-
- case FlushingState:
- if (this.level != NoCompression)
- {
- // We have to supply some lookahead. 8 bit lookahead
- // is needed by the zlib inflater, and we must fill
- // the next byte, so that all bits are flushed.
- int neededbits = 8 + ((-this.engine.Pending.BitCount) & 7);
- while (neededbits > 0)
- {
- // Write a static tree block consisting solely of an EOF:
- this.engine.Pending.WriteBits(2, 10);
- neededbits -= 10;
- }
- }
-
- this.state = BusyState;
- break;
-
- case FinishingState:
- this.engine.Pending.AlignToByte();
- this.state = FinishedState;
- break;
- }
- }
- }
-
- return origLength - length;
- }
-
- ///
- public void Dispose()
- {
- if (!this.isDisposed)
- {
- this.engine.Dispose();
- this.isDisposed = true;
- }
- }
-}
diff --git a/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs b/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
deleted file mode 100644
index fbc2083b3..000000000
--- a/src/ImageSharp/Compression/Zlib/DeflaterConstants.cs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-//
-using System;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// This class contains constants used for deflation.
-///
-internal static class DeflaterConstants
-{
- ///
- /// Set to true to enable debugging
- ///
- public const bool DEBUGGING = false;
-
- ///
- /// Written to Zip file to identify a stored block
- ///
- public const int STORED_BLOCK = 0;
-
- ///
- /// Identifies static tree in Zip file
- ///
- public const int STATIC_TREES = 1;
-
- ///
- /// Identifies dynamic tree in Zip file
- ///
- public const int DYN_TREES = 2;
-
- ///
- /// Header flag indicating a preset dictionary for deflation
- ///
- public const int PRESET_DICT = 0x20;
-
- ///
- /// Sets internal buffer sizes for Huffman encoding
- ///
- public const int DEFAULT_MEM_LEVEL = 8;
-
- ///
- /// Internal compression engine constant
- ///
- public const int MAX_MATCH = 258;
-
- ///
- /// Internal compression engine constant
- ///
- public const int MIN_MATCH = 3;
-
- ///
- /// Internal compression engine constant
- ///
- public const int MAX_WBITS = 15;
-
- ///
- /// Internal compression engine constant
- ///
- public const int WSIZE = 1 << MAX_WBITS;
-
- ///
- /// Internal compression engine constant
- ///
- public const int WMASK = WSIZE - 1;
-
- ///
- /// Internal compression engine constant
- ///
- public const int HASH_BITS = DEFAULT_MEM_LEVEL + 7;
-
- ///
- /// Internal compression engine constant
- ///
- public const int HASH_SIZE = 1 << HASH_BITS;
-
- ///
- /// Internal compression engine constant
- ///
- public const int HASH_MASK = HASH_SIZE - 1;
-
- ///
- /// Internal compression engine constant
- ///
- public const int HASH_SHIFT = (HASH_BITS + MIN_MATCH - 1) / MIN_MATCH;
-
- ///
- /// Internal compression engine constant
- ///
- public const int MIN_LOOKAHEAD = MAX_MATCH + MIN_MATCH + 1;
-
- ///
- /// Internal compression engine constant
- ///
- public const int MAX_DIST = WSIZE - MIN_LOOKAHEAD;
-
- ///
- /// Internal compression engine constant
- ///
- public const int PENDING_BUF_SIZE = 1 << (DEFAULT_MEM_LEVEL + 8);
-
- ///
- /// Internal compression engine constant
- ///
- public static int MAX_BLOCK_SIZE = Math.Min(65535, PENDING_BUF_SIZE - 5);
-
- ///
- /// Internal compression engine constant
- ///
- public const int DEFLATE_STORED = 0;
-
- ///
- /// Internal compression engine constant
- ///
- public const int DEFLATE_FAST = 1;
-
- ///
- /// Internal compression engine constant
- ///
- public const int DEFLATE_SLOW = 2;
-
- ///
- /// Internal compression engine constant
- ///
- public static int[] GOOD_LENGTH = [0, 4, 4, 4, 4, 8, 8, 8, 32, 32];
-
- ///
- /// Internal compression engine constant
- ///
- public static int[] MAX_LAZY = [0, 4, 5, 6, 4, 16, 16, 32, 128, 258];
-
- ///
- /// Internal compression engine constant
- ///
- public static int[] NICE_LENGTH = [0, 8, 16, 32, 16, 32, 128, 128, 258, 258];
-
- ///
- /// Internal compression engine constant
- ///
- public static int[] MAX_CHAIN = [0, 4, 8, 32, 16, 32, 128, 256, 1024, 4096];
-
- ///
- /// Internal compression engine constant
- ///
- public static int[] COMPR_FUNC = [0, 1, 1, 1, 1, 2, 2, 2, 2, 2];
-}
diff --git a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs b/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
deleted file mode 100644
index 6009fdfbc..000000000
--- a/src/ImageSharp/Compression/Zlib/DeflaterEngine.cs
+++ /dev/null
@@ -1,867 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Buffers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// Strategies for deflater
-///
-internal enum DeflateStrategy
-{
- ///
- /// The default strategy
- ///
- Default = 0,
-
- ///
- /// This strategy will only allow longer string repetitions. It is
- /// useful for random data with a small character set.
- ///
- Filtered = 1,
-
- ///
- /// This strategy will not look for string repetitions at all. It
- /// only encodes with Huffman trees (which means, that more common
- /// characters get a smaller encoding.
- ///
- HuffmanOnly = 2
-}
-
-// DEFLATE ALGORITHM:
-//
-// The uncompressed stream is inserted into the window array. When
-// the window array is full the first half is thrown away and the
-// second half is copied to the beginning.
-//
-// The head array is a hash table. Three characters build a hash value
-// and they the value points to the corresponding index in window of
-// the last string with this hash. The prev array implements a
-// linked list of matches with the same hash: prev[index & WMASK] points
-// to the previous index with the same hash.
-//
-
-///
-/// Low level compression engine for deflate algorithm which uses a 32K sliding window
-/// with secondary compression from Huffman/Shannon-Fano codes.
-///
-internal sealed unsafe class DeflaterEngine : IDisposable
-{
- private const int TooFar = 4096;
-
- // Hash index of string to be inserted
- private int insertHashIndex;
-
- private int matchStart;
-
- // Length of best match
- private int matchLen;
-
- // Set if previous match exists
- private bool prevAvailable;
-
- private int blockStart;
-
- ///
- /// Points to the current character in the window.
- ///
- private int strstart;
-
- ///
- /// lookahead is the number of characters starting at strstart in
- /// window that are valid.
- /// So window[strstart] until window[strstart+lookahead-1] are valid
- /// characters.
- ///
- private int lookahead;
-
- ///
- /// The current compression function.
- ///
- private int compressionFunction;
-
- ///
- /// The input data for compression.
- ///
- private byte[]? inputBuf;
-
- ///
- /// The offset into inputBuf, where input data starts.
- ///
- private int inputOff;
-
- ///
- /// The end offset of the input data.
- ///
- private int inputEnd;
-
- private readonly DeflateStrategy strategy;
- private DeflaterHuffman huffman;
- private bool isDisposed;
-
- ///
- /// Hashtable, hashing three characters to an index for window, so
- /// that window[index]..window[index+2] have this hash code.
- /// Note that the array should really be unsigned short, so you need
- /// to and the values with 0xFFFF.
- ///
- private IMemoryOwner headMemoryOwner;
- private MemoryHandle headMemoryHandle;
- private readonly Memory head;
- private readonly short* pinnedHeadPointer;
-
- ///
- /// prev[index & WMASK] points to the previous index that has the
- /// same hash code as the string starting at index. This way
- /// entries with the same hash code are in a linked list.
- /// Note that the array should really be unsigned short, so you need
- /// to and the values with 0xFFFF.
- ///
- private IMemoryOwner prevMemoryOwner;
- private MemoryHandle prevMemoryHandle;
- private readonly Memory prev;
- private readonly short* pinnedPrevPointer;
-
- ///
- /// This array contains the part of the uncompressed stream that
- /// is of relevance. The current character is indexed by strstart.
- ///
- private IMemoryOwner windowMemoryOwner;
- private MemoryHandle windowMemoryHandle;
- private readonly Memory window;
- private readonly byte* pinnedWindowPointer;
-
- private int maxChain;
- private int maxLazy;
- private int niceLength;
- private int goodLength;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- /// The deflate strategy to use.
- public DeflaterEngine(MemoryAllocator memoryAllocator, DeflateStrategy strategy)
- {
- this.huffman = new DeflaterHuffman(memoryAllocator);
- this.Pending = this.huffman.Pending;
- this.strategy = strategy;
-
- // Create pinned pointers to the various buffers to allow indexing
- // without bounds checks.
- this.windowMemoryOwner = memoryAllocator.Allocate(2 * DeflaterConstants.WSIZE);
- this.window = this.windowMemoryOwner.Memory;
- this.windowMemoryHandle = this.window.Pin();
- this.pinnedWindowPointer = (byte*)this.windowMemoryHandle.Pointer;
-
- this.headMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.HASH_SIZE);
- this.head = this.headMemoryOwner.Memory;
- this.headMemoryHandle = this.head.Pin();
- this.pinnedHeadPointer = (short*)this.headMemoryHandle.Pointer;
-
- this.prevMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.WSIZE);
- this.prev = this.prevMemoryOwner.Memory;
- this.prevMemoryHandle = this.prev.Pin();
- this.pinnedPrevPointer = (short*)this.prevMemoryHandle.Pointer;
-
- // We start at index 1, to avoid an implementation deficiency, that
- // we cannot build a repeat pattern at index 0.
- this.blockStart = this.strstart = 1;
- }
-
- ///
- /// Gets the pending buffer to use.
- ///
- public DeflaterPendingBuffer Pending { get; }
-
- ///
- /// Deflate drives actual compression of data
- ///
- /// True to flush input buffers
- /// Finish deflation with the current input.
- /// Returns true if progress has been made.
- public bool Deflate(bool flush, bool finish)
- {
- bool progress = false;
- do
- {
- this.FillWindow();
- bool canFlush = flush && (this.inputOff == this.inputEnd);
-
- switch (this.compressionFunction)
- {
- case DeflaterConstants.DEFLATE_STORED:
- progress = this.DeflateStored(canFlush, finish);
- break;
-
- case DeflaterConstants.DEFLATE_FAST:
- progress = this.DeflateFast(canFlush, finish);
- break;
-
- case DeflaterConstants.DEFLATE_SLOW:
- progress = this.DeflateSlow(canFlush, finish);
- break;
-
- default:
- DeflateThrowHelper.ThrowUnknownCompression();
- break;
- }
- }
- while (this.Pending.IsFlushed && progress); // repeat while we have no pending output and progress was made
- return progress;
- }
-
- ///
- /// Sets input data to be deflated. Should only be called when
- /// returns true
- ///
- /// The buffer containing input data.
- /// The offset of the first byte of data.
- /// The number of bytes of data to use as input.
- public void SetInput(byte[]? buffer, int offset, int count)
- {
- if (buffer is null)
- {
- DeflateThrowHelper.ThrowNull(nameof(buffer));
- }
-
- if (offset < 0)
- {
- DeflateThrowHelper.ThrowOutOfRange(nameof(offset));
- }
-
- if (count < 0)
- {
- DeflateThrowHelper.ThrowOutOfRange(nameof(count));
- }
-
- if (this.inputOff < this.inputEnd)
- {
- DeflateThrowHelper.ThrowNotProcessed();
- }
-
- int end = offset + count;
-
- // We want to throw an ArgumentOutOfRangeException early.
- // The check is very tricky: it also handles integer wrap around.
- if ((offset > end) || (end > buffer.Length))
- {
- DeflateThrowHelper.ThrowOutOfRange(nameof(count));
- }
-
- this.inputBuf = buffer;
- this.inputOff = offset;
- this.inputEnd = end;
- }
-
- ///
- /// Determines if more input is needed.
- ///
- /// Return true if input is needed via SetInput
- [MethodImpl(InliningOptions.ShortMethod)]
- public bool NeedsInput() => this.inputEnd == this.inputOff;
-
- ///
- /// Reset internal state
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Reset()
- {
- this.huffman.Reset();
- this.blockStart = this.strstart = 1;
- this.lookahead = 0;
- this.prevAvailable = false;
- this.matchLen = DeflaterConstants.MIN_MATCH - 1;
- this.head.Span[..DeflaterConstants.HASH_SIZE].Clear();
- this.prev.Span[..DeflaterConstants.WSIZE].Clear();
- }
-
- ///
- /// Set the deflate level (0-9)
- ///
- /// The value to set the level to.
- public void SetLevel(int level)
- {
- if (level is < 0 or > 9)
- {
- DeflateThrowHelper.ThrowOutOfRange(nameof(level));
- }
-
- this.goodLength = DeflaterConstants.GOOD_LENGTH[level];
- this.maxLazy = DeflaterConstants.MAX_LAZY[level];
- this.niceLength = DeflaterConstants.NICE_LENGTH[level];
- this.maxChain = DeflaterConstants.MAX_CHAIN[level];
-
- if (DeflaterConstants.COMPR_FUNC[level] != this.compressionFunction)
- {
- switch (this.compressionFunction)
- {
- case DeflaterConstants.DEFLATE_STORED:
- if (this.strstart > this.blockStart)
- {
- this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
- this.blockStart = this.strstart;
- }
-
- this.UpdateHash();
- break;
-
- case DeflaterConstants.DEFLATE_FAST:
- if (this.strstart > this.blockStart)
- {
- this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
- this.blockStart = this.strstart;
- }
-
- break;
-
- case DeflaterConstants.DEFLATE_SLOW:
- if (this.prevAvailable)
- {
- this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xFF);
- }
-
- if (this.strstart > this.blockStart)
- {
- this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, false);
- this.blockStart = this.strstart;
- }
-
- this.prevAvailable = false;
- this.matchLen = DeflaterConstants.MIN_MATCH - 1;
- break;
- }
-
- this.compressionFunction = DeflaterConstants.COMPR_FUNC[level];
- }
- }
-
- ///
- /// Fill the window
- ///
- public void FillWindow()
- {
- // If the window is almost full and there is insufficient lookahead,
- // move the upper half to the lower one to make room in the upper half.
- if (this.strstart >= DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)
- {
- this.SlideWindow();
- }
-
- // If there is not enough lookahead, but still some input left, read in the input.
- if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && this.inputOff < this.inputEnd)
- {
- int more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart;
-
- if (more > this.inputEnd - this.inputOff)
- {
- more = this.inputEnd - this.inputOff;
- }
-
- ArgumentNullException.ThrowIfNull(this.inputBuf);
-
- Unsafe.CopyBlockUnaligned(
- ref this.window.Span[this.strstart + this.lookahead],
- ref this.inputBuf[this.inputOff],
- unchecked((uint)more));
-
- this.inputOff += more;
- this.lookahead += more;
- }
-
- if (this.lookahead >= DeflaterConstants.MIN_MATCH)
- {
- this.UpdateHash();
- }
- }
-
- ///
- public void Dispose()
- {
- if (!this.isDisposed)
- {
- this.huffman.Dispose();
-
- this.windowMemoryHandle.Dispose();
- this.windowMemoryOwner.Dispose();
-
- this.headMemoryHandle.Dispose();
- this.headMemoryOwner.Dispose();
-
- this.prevMemoryHandle.Dispose();
- this.prevMemoryOwner.Dispose();
-
- this.isDisposed = true;
- }
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private void UpdateHash()
- {
- byte* pinned = this.pinnedWindowPointer;
- this.insertHashIndex = (pinned[this.strstart] << DeflaterConstants.HASH_SHIFT) ^ pinned[this.strstart + 1];
- }
-
- ///
- /// Inserts the current string in the head hash and returns the previous
- /// value for this hash.
- ///
- /// The previous hash value
- [MethodImpl(InliningOptions.ShortMethod)]
- private int InsertString()
- {
- short match;
- int hash = ((this.insertHashIndex << DeflaterConstants.HASH_SHIFT) ^ this.pinnedWindowPointer[this.strstart + (DeflaterConstants.MIN_MATCH - 1)]) & DeflaterConstants.HASH_MASK;
-
- short* pinnedHead = this.pinnedHeadPointer;
- this.pinnedPrevPointer[this.strstart & DeflaterConstants.WMASK] = match = pinnedHead[hash];
- pinnedHead[hash] = unchecked((short)this.strstart);
- this.insertHashIndex = hash;
- return match & 0xFFFF;
- }
-
- private void SlideWindow()
- {
- Unsafe.CopyBlockUnaligned(
- ref MemoryMarshal.GetReference(this.window.Span),
- ref Unsafe.Add(ref MemoryMarshal.GetReference(this.window.Span), DeflaterConstants.WSIZE),
- DeflaterConstants.WSIZE);
-
- this.matchStart -= DeflaterConstants.WSIZE;
- this.strstart -= DeflaterConstants.WSIZE;
- this.blockStart -= DeflaterConstants.WSIZE;
-
- // Slide the hash table (could be avoided with 32 bit values
- // at the expense of memory usage).
- short* pinnedHead = this.pinnedHeadPointer;
- for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i)
- {
- int m = pinnedHead[i] & 0xFFFF;
- pinnedHead[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
- }
-
- // Slide the prev table.
- short* pinnedPrev = this.pinnedPrevPointer;
- for (int i = 0; i < DeflaterConstants.WSIZE; i++)
- {
- int m = pinnedPrev[i] & 0xFFFF;
- pinnedPrev[i] = (short)(m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
- }
- }
-
- ///
- ///
- /// Find the best (longest) string in the window matching the
- /// string starting at strstart.
- ///
- ///
- /// Preconditions:
- ///
- /// strstart + DeflaterConstants.MAX_MATCH <= window.length.
- ///
- ///
- /// The current match.
- /// True if a match greater than the minimum length is found
- [MethodImpl(InliningOptions.HotPath)]
- private bool FindLongestMatch(int curMatch)
- {
- int match;
- int scan = this.strstart;
-
- // scanMax is the highest position that we can look at
- int scanMax = scan + Math.Min(DeflaterConstants.MAX_MATCH, this.lookahead) - 1;
- int limit = Math.Max(scan - DeflaterConstants.MAX_DIST, 0);
-
- int chainLength = this.maxChain;
- int niceLength = Math.Min(this.niceLength, this.lookahead);
-
- int matchStrt = this.matchStart;
- int matchLength = this.matchLen;
- matchLength = Math.Max(matchLength, DeflaterConstants.MIN_MATCH - 1);
- this.matchLen = matchLength;
-
- if (scan > scanMax - matchLength)
- {
- return false;
- }
-
- int scanEndPosition = scan + matchLength;
-
- byte* pinnedWindow = this.pinnedWindowPointer;
- int scanStart = this.strstart;
- byte scanEnd1 = pinnedWindow[scanEndPosition - 1];
- byte scanEnd = pinnedWindow[scanEndPosition];
-
- // Do not waste too much time if we already have a good match:
- if (matchLength >= this.goodLength)
- {
- chainLength >>= 2;
- }
-
- short* pinnedPrev = this.pinnedPrevPointer;
- do
- {
- match = curMatch;
- scan = scanStart;
-
- int matchEndPosition = match + matchLength;
- if (pinnedWindow[matchEndPosition] != scanEnd
- || pinnedWindow[matchEndPosition - 1] != scanEnd1
- || pinnedWindow[match] != pinnedWindow[scan]
- || pinnedWindow[++match] != pinnedWindow[++scan])
- {
- continue;
- }
-
- // scan is set to strstart+1 and the comparison passed, so
- // scanMax - scan is the maximum number of bytes we can compare.
- // below we compare 8 bytes at a time, so first we compare
- // (scanMax - scan) % 8 bytes, so the remainder is a multiple of 8
- // n & (8 - 1) == n % 8.
- switch ((scanMax - scan) & 7)
- {
- case 1:
- if (pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
-
- case 2:
- if (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
-
- case 3:
- if (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
-
- case 4:
- if (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
-
- case 5:
- if (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
-
- case 6:
- if (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
-
- case 7:
- if (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match])
- {
- break;
- }
-
- break;
- }
-
- if (pinnedWindow[scan] == pinnedWindow[match])
- {
- // We check for insufficient lookahead only every 8th comparison;
- // the 256th check will be made at strstart + 258 unless lookahead is
- // exhausted first.
- do
- {
- if (scan == scanMax)
- {
- ++scan; // advance to first position not matched
- ++match;
-
- break;
- }
- }
- while (pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]
- && pinnedWindow[++scan] == pinnedWindow[++match]);
- }
-
- if (scan - scanStart > matchLength)
- {
- matchStrt = curMatch;
- matchLength = scan - scanStart;
-
- if (matchLength >= niceLength)
- {
- break;
- }
-
- scanEnd1 = pinnedWindow[scan - 1];
- scanEnd = pinnedWindow[scan];
- }
- }
- while ((curMatch = pinnedPrev[curMatch & DeflaterConstants.WMASK] & 0xFFFF) > limit && --chainLength != 0);
-
- this.matchStart = matchStrt;
- this.matchLen = matchLength;
- return matchLength >= DeflaterConstants.MIN_MATCH;
- }
-
- private bool DeflateStored(bool flush, bool finish)
- {
- if (!flush && (this.lookahead == 0))
- {
- return false;
- }
-
- this.strstart += this.lookahead;
- this.lookahead = 0;
-
- int storedLength = this.strstart - this.blockStart;
-
- if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || // Block is full
- (this.blockStart < DeflaterConstants.WSIZE && storedLength >= DeflaterConstants.MAX_DIST) || // Block may move out of window
- flush)
- {
- bool lastBlock = finish;
- if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE)
- {
- storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
- lastBlock = false;
- }
-
- this.huffman.FlushStoredBlock(this.window.Span, this.blockStart, storedLength, lastBlock);
- this.blockStart += storedLength;
- return !(lastBlock || storedLength == 0);
- }
-
- return true;
- }
-
- private bool DeflateFast(bool flush, bool finish)
- {
- if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
- {
- return false;
- }
-
- const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD;
- while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
- {
- if (this.lookahead == 0)
- {
- // We are flushing everything
- this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish);
- this.blockStart = this.strstart;
- return false;
- }
-
- if (this.strstart > windowLen)
- {
- // slide window, as FindLongestMatch needs this.
- // This should only happen when flushing and the window
- // is almost full.
- this.SlideWindow();
- }
-
- int hashHead;
- if (this.lookahead >= DeflaterConstants.MIN_MATCH &&
- (hashHead = this.InsertString()) != 0 &&
- this.strategy != DeflateStrategy.HuffmanOnly &&
- this.strstart - hashHead <= DeflaterConstants.MAX_DIST &&
- this.FindLongestMatch(hashHead))
- {
- // longestMatch sets matchStart and matchLen
- bool full = this.huffman.TallyDist(this.strstart - this.matchStart, this.matchLen);
-
- this.lookahead -= this.matchLen;
- if (this.matchLen <= this.maxLazy && this.lookahead >= DeflaterConstants.MIN_MATCH)
- {
- while (--this.matchLen > 0)
- {
- ++this.strstart;
- this.InsertString();
- }
-
- ++this.strstart;
- }
- else
- {
- this.strstart += this.matchLen;
- if (this.lookahead >= DeflaterConstants.MIN_MATCH - 1)
- {
- this.UpdateHash();
- }
- }
-
- this.matchLen = DeflaterConstants.MIN_MATCH - 1;
- if (!full)
- {
- continue;
- }
- }
- else
- {
- // No match found
- this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart] & 0xff);
- ++this.strstart;
- --this.lookahead;
- }
-
- if (this.huffman.IsFull())
- {
- bool lastBlock = finish && (this.lookahead == 0);
- this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, lastBlock);
- this.blockStart = this.strstart;
- return !lastBlock;
- }
- }
-
- return true;
- }
-
- private bool DeflateSlow(bool flush, bool finish)
- {
- if (this.lookahead < DeflaterConstants.MIN_LOOKAHEAD && !flush)
- {
- return false;
- }
-
- const int windowLen = (2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD;
- while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush)
- {
- if (this.lookahead == 0)
- {
- if (this.prevAvailable)
- {
- this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff);
- }
-
- this.prevAvailable = false;
-
- // We are flushing everything
- this.huffman.FlushBlock(this.window.Span, this.blockStart, this.strstart - this.blockStart, finish);
- this.blockStart = this.strstart;
- return false;
- }
-
- if (this.strstart >= windowLen)
- {
- // slide window, as FindLongestMatch needs this.
- // This should only happen when flushing and the window
- // is almost full.
- this.SlideWindow();
- }
-
- int prevMatch = this.matchStart;
- int prevLen = this.matchLen;
- if (this.lookahead >= DeflaterConstants.MIN_MATCH)
- {
- int hashHead = this.InsertString();
-
- if (this.strategy != DeflateStrategy.HuffmanOnly &&
- hashHead != 0 &&
- this.strstart - hashHead <= DeflaterConstants.MAX_DIST &&
- this.FindLongestMatch(hashHead))
- {
- // longestMatch sets matchStart and matchLen
- // Discard match if too small and too far away
- if (this.matchLen <= 5 && (this.strategy == DeflateStrategy.Filtered || (this.matchLen == DeflaterConstants.MIN_MATCH && this.strstart - this.matchStart > TooFar)))
- {
- this.matchLen = DeflaterConstants.MIN_MATCH - 1;
- }
- }
- }
-
- // previous match was better
- if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen))
- {
- this.huffman.TallyDist(this.strstart - 1 - prevMatch, prevLen);
- prevLen -= 2;
- do
- {
- this.strstart++;
- this.lookahead--;
- if (this.lookahead >= DeflaterConstants.MIN_MATCH)
- {
- this.InsertString();
- }
- }
- while (--prevLen > 0);
-
- this.strstart++;
- this.lookahead--;
- this.prevAvailable = false;
- this.matchLen = DeflaterConstants.MIN_MATCH - 1;
- }
- else
- {
- if (this.prevAvailable)
- {
- this.huffman.TallyLit(this.pinnedWindowPointer[this.strstart - 1] & 0xff);
- }
-
- this.prevAvailable = true;
- this.strstart++;
- this.lookahead--;
- }
-
- if (this.huffman.IsFull())
- {
- int len = this.strstart - this.blockStart;
- if (this.prevAvailable)
- {
- len--;
- }
-
- bool lastBlock = finish && (this.lookahead == 0) && !this.prevAvailable;
- this.huffman.FlushBlock(this.window.Span, this.blockStart, len, lastBlock);
- this.blockStart += len;
- return !lastBlock;
- }
- }
-
- return true;
- }
-}
diff --git a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs b/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
deleted file mode 100644
index 17cb1925d..000000000
--- a/src/ImageSharp/Compression/Zlib/DeflaterHuffman.cs
+++ /dev/null
@@ -1,979 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Buffers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// Performs Deflate Huffman encoding.
-///
-internal sealed unsafe class DeflaterHuffman : IDisposable
-{
- private const int BufferSize = 1 << (DeflaterConstants.DEFAULT_MEM_LEVEL + 6);
-
- // The number of literal codes.
- private const int LiteralNumber = 286;
-
- // Number of distance codes
- private const int DistanceNumber = 30;
-
- // Number of codes used to transfer bit lengths
- private const int BitLengthNumber = 19;
-
- // Repeat previous bit length 3-6 times (2 bits of repeat count)
- private const int Repeat3To6 = 16;
-
- // Repeat a zero length 3-10 times (3 bits of repeat count)
- private const int Repeat3To10 = 17;
-
- // Repeat a zero length 11-138 times (7 bits of repeat count)
- private const int Repeat11To138 = 18;
-
- private const int EofSymbol = 256;
-
- private Tree literalTree;
- private Tree distTree;
- private Tree blTree;
-
- // Buffer for distances
- private readonly IMemoryOwner distanceMemoryOwner;
- private readonly short* pinnedDistanceBuffer;
- private MemoryHandle distanceBufferHandle;
-
- private readonly IMemoryOwner literalMemoryOwner;
- private readonly short* pinnedLiteralBuffer;
- private MemoryHandle literalBufferHandle;
-
- private int lastLiteral;
- private int extraBits;
- private bool isDisposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- public DeflaterHuffman(MemoryAllocator memoryAllocator)
- {
- this.Pending = new DeflaterPendingBuffer(memoryAllocator);
-
- this.literalTree = new Tree(memoryAllocator, LiteralNumber, 257, 15);
- this.distTree = new Tree(memoryAllocator, DistanceNumber, 1, 15);
- this.blTree = new Tree(memoryAllocator, BitLengthNumber, 4, 7);
-
- this.distanceMemoryOwner = memoryAllocator.Allocate(BufferSize);
- this.distanceBufferHandle = this.distanceMemoryOwner.Memory.Pin();
- this.pinnedDistanceBuffer = (short*)this.distanceBufferHandle.Pointer;
-
- this.literalMemoryOwner = memoryAllocator.Allocate(BufferSize);
- this.literalBufferHandle = this.literalMemoryOwner.Memory.Pin();
- this.pinnedLiteralBuffer = (short*)this.literalBufferHandle.Pointer;
- }
-
-#pragma warning disable SA1201 // Elements should appear in the correct order
-
- // See RFC 1951 3.2.6
- // Literal codes
- private static readonly short[] StaticLCodes =
- [
- 12, 140, 76, 204, 44, 172, 108, 236, 28, 156, 92, 220, 60, 188, 124, 252,
- 2, 130, 66, 194, 34, 162, 98, 226, 18, 146, 82, 210, 50, 178, 114, 242,
- 10, 138, 74, 202, 42, 170, 106, 234, 26, 154, 90, 218, 58, 186, 122, 250,
- 6, 134, 70, 198, 38, 166, 102, 230, 22, 150, 86, 214, 54, 182, 118, 246,
- 14, 142, 78, 206, 46, 174, 110, 238, 30, 158, 94, 222, 62, 190, 126, 254,
- 1, 129, 65, 193, 33, 161, 97, 225, 17, 145, 81, 209, 49, 177, 113, 241, 9,
- 137, 73, 201, 41, 169, 105, 233, 25, 153, 89, 217, 57, 185, 121, 249, 5,
- 133, 69, 197, 37, 165, 101, 229, 21, 149, 85, 213, 53, 181, 117, 245, 13,
- 141, 77, 205, 45, 173, 109, 237, 29, 157, 93, 221, 61, 189, 125, 253, 19,
- 275, 147, 403, 83, 339, 211, 467, 51, 307, 179, 435, 115, 371, 243, 499,
- 11, 267, 139, 395, 75, 331, 203, 459, 43, 299, 171, 427, 107, 363, 235, 491,
- 27, 283, 155, 411, 91, 347, 219, 475, 59, 315, 187, 443, 123, 379, 251, 507,
- 7, 263, 135, 391, 71, 327, 199, 455, 39, 295, 167, 423, 103, 359, 231, 487,
- 23, 279, 151, 407, 87, 343, 215, 471, 55, 311, 183, 439, 119, 375, 247, 503,
- 15, 271, 143, 399, 79, 335, 207, 463, 47, 303, 175, 431, 111, 367, 239, 495,
- 31, 287, 159, 415, 95, 351, 223, 479, 63, 319, 191, 447, 127, 383, 255, 511,
- 0, 64, 32, 96, 16, 80, 48, 112, 8, 72, 40, 104, 24, 88, 56, 120, 4, 68, 36,
- 100, 20, 84, 52, 116, 3, 131, 67, 195, 35, 163
- ];
-
- private static ReadOnlySpan StaticLLength =>
- [
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8
- ];
-
- // Distance codes and lengths.
- private static readonly short[] StaticDCodes =
- [
- 0, 16, 8, 24, 4, 20, 12, 28, 2, 18, 10, 26, 6, 22, 14,
- 30, 1, 17, 9, 25, 5, 21, 13, 29, 3, 19, 11, 27, 7, 23
- ];
-
- private static ReadOnlySpan StaticDLength =>
- [
- 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
- 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
- ];
-#pragma warning restore SA1201 // Elements should appear in the correct order
-
- ///
- /// Gets the lengths of the bit length codes are sent in order of decreasing probability, to avoid transmitting the lengths for unused bit length codes.
- ///
- private static ReadOnlySpan BitLengthOrder =>
- [
- 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
- ];
-
- private static ReadOnlySpan Bit4Reverse =>
- [
- 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
- ];
-
- ///
- /// Gets the pending buffer to use.
- ///
- public DeflaterPendingBuffer Pending { get; private set; }
-
- ///
- /// Reset internal state
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Reset()
- {
- this.lastLiteral = 0;
- this.extraBits = 0;
- this.literalTree.Reset();
- this.distTree.Reset();
- this.blTree.Reset();
- }
-
- ///
- /// Write all trees to pending buffer
- ///
- /// The number/rank of treecodes to send.
- public void SendAllTrees(int blTreeCodes)
- {
- this.blTree.BuildCodes();
- this.literalTree.BuildCodes();
- this.distTree.BuildCodes();
- this.Pending.WriteBits(this.literalTree.NumCodes - 257, 5);
- this.Pending.WriteBits(this.distTree.NumCodes - 1, 5);
- this.Pending.WriteBits(blTreeCodes - 4, 4);
-
- for (int rank = 0; rank < blTreeCodes; rank++)
- {
- this.Pending.WriteBits(this.blTree.Length[BitLengthOrder[rank]], 3);
- }
-
- this.literalTree.WriteTree(this.Pending, this.blTree);
- this.distTree.WriteTree(this.Pending, this.blTree);
- }
-
- ///
- /// Compress current buffer writing data to pending buffer
- ///
- public void CompressBlock()
- {
- DeflaterPendingBuffer pendingBuffer = this.Pending;
- short* pinnedDistance = this.pinnedDistanceBuffer;
- short* pinnedLiteral = this.pinnedLiteralBuffer;
-
- for (int i = 0; i < this.lastLiteral; i++)
- {
- int litlen = pinnedLiteral[i] & 0xFF;
- int dist = pinnedDistance[i];
- if (dist-- != 0)
- {
- int lc = Lcode(litlen);
- this.literalTree.WriteSymbol(pendingBuffer, lc);
-
- int bits = (int)(((uint)lc - 261) / 4);
- if (bits is > 0 and <= 5)
- {
- this.Pending.WriteBits(litlen & ((1 << bits) - 1), bits);
- }
-
- int dc = Dcode(dist);
- this.distTree.WriteSymbol(pendingBuffer, dc);
-
- bits = (dc >> 1) - 1;
- if (bits > 0)
- {
- this.Pending.WriteBits(dist & ((1 << bits) - 1), bits);
- }
- }
- else
- {
- this.literalTree.WriteSymbol(pendingBuffer, litlen);
- }
- }
-
- this.literalTree.WriteSymbol(pendingBuffer, EofSymbol);
- }
-
- ///
- /// Flush block to output with no compression
- ///
- /// Data to write
- /// Index of first byte to write
- /// Count of bytes to write
- /// True if this is the last block
- [MethodImpl(InliningOptions.ShortMethod)]
- public void FlushStoredBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock)
- {
- this.Pending.WriteBits((DeflaterConstants.STORED_BLOCK << 1) + (lastBlock ? 1 : 0), 3);
- this.Pending.AlignToByte();
- this.Pending.WriteShort(storedLength);
- this.Pending.WriteShort(~storedLength);
- this.Pending.WriteBlock(stored, storedOffset, storedLength);
- this.Reset();
- }
-
- ///
- /// Flush block to output with compression
- ///
- /// Data to flush
- /// Index of first byte to flush
- /// Count of bytes to flush
- /// True if this is the last block
- public void FlushBlock(ReadOnlySpan stored, int storedOffset, int storedLength, bool lastBlock)
- {
- this.literalTree.Frequencies[EofSymbol]++;
-
- // Build trees
- this.literalTree.BuildTree();
- this.distTree.BuildTree();
-
- // Calculate bitlen frequency
- this.literalTree.CalcBLFreq(this.blTree);
- this.distTree.CalcBLFreq(this.blTree);
-
- // Build bitlen tree
- this.blTree.BuildTree();
-
- int blTreeCodes = 4;
-
- for (int i = 18; i > blTreeCodes; i--)
- {
- if (this.blTree.Length[BitLengthOrder[i]] > 0)
- {
- blTreeCodes = i + 1;
- }
- }
-
- int opt_len = 14 + (blTreeCodes * 3) + this.blTree.GetEncodedLength()
- + this.literalTree.GetEncodedLength() + this.distTree.GetEncodedLength()
- + this.extraBits;
-
- int static_len = this.extraBits;
- ref byte staticLLengthRef = ref MemoryMarshal.GetReference(StaticLLength);
- for (nuint i = 0; i < LiteralNumber; i++)
- {
- static_len += this.literalTree.Frequencies[i] * Unsafe.Add(ref staticLLengthRef, i);
- }
-
- ref byte staticDLengthRef = ref MemoryMarshal.GetReference(StaticDLength);
- for (nuint i = 0; i < DistanceNumber; i++)
- {
- static_len += this.distTree.Frequencies[i] * Unsafe.Add(ref staticDLengthRef, i);
- }
-
- if (opt_len >= static_len)
- {
- // Force static trees
- opt_len = static_len;
- }
-
- if (storedOffset >= 0 && storedLength + 4 < opt_len >> 3)
- {
- // Store Block
- this.FlushStoredBlock(stored, storedOffset, storedLength, lastBlock);
- }
- else if (opt_len == static_len)
- {
- // Encode with static tree
- this.Pending.WriteBits((DeflaterConstants.STATIC_TREES << 1) + (lastBlock ? 1 : 0), 3);
- this.literalTree.SetStaticCodes(StaticLCodes, StaticLLength);
- this.distTree.SetStaticCodes(StaticDCodes, StaticDLength);
- this.CompressBlock();
- this.Reset();
- }
- else
- {
- // Encode with dynamic tree
- this.Pending.WriteBits((DeflaterConstants.DYN_TREES << 1) + (lastBlock ? 1 : 0), 3);
- this.SendAllTrees(blTreeCodes);
- this.CompressBlock();
- this.Reset();
- }
- }
-
- ///
- /// Get value indicating if internal buffer is full
- ///
- /// true if buffer is full
- [MethodImpl(InliningOptions.ShortMethod)]
- public bool IsFull() => this.lastLiteral >= BufferSize;
-
- ///
- /// Add literal to buffer
- ///
- /// Literal value to add to buffer.
- /// Value indicating internal buffer is full
- [MethodImpl(InliningOptions.ShortMethod)]
- public bool TallyLit(int literal)
- {
- this.pinnedDistanceBuffer[this.lastLiteral] = 0;
- this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)literal;
- this.literalTree.Frequencies[literal]++;
- return this.IsFull();
- }
-
- ///
- /// Add distance code and length to literal and distance trees
- ///
- /// Distance code
- /// Length
- /// Value indicating if internal buffer is full
- [MethodImpl(InliningOptions.ShortMethod)]
- public bool TallyDist(int distance, int length)
- {
- this.pinnedDistanceBuffer[this.lastLiteral] = (short)distance;
- this.pinnedLiteralBuffer[this.lastLiteral++] = (byte)(length - 3);
-
- int lc = Lcode(length - 3);
- this.literalTree.Frequencies[lc]++;
- if (lc >= 265 && lc < 285)
- {
- this.extraBits += (int)(((uint)lc - 261) / 4);
- }
-
- int dc = Dcode(distance - 1);
- this.distTree.Frequencies[dc]++;
- if (dc >= 4)
- {
- this.extraBits += (dc >> 1) - 1;
- }
-
- return this.IsFull();
- }
-
- ///
- /// Reverse the bits of a 16 bit value.
- ///
- /// Value to reverse bits
- /// Value with bits reversed
- [MethodImpl(InliningOptions.ShortMethod)]
- public static short BitReverse(int toReverse)
- {
- /* Use unsafe offsetting and manually validate the input index to reduce the
- * total number of conditional branches. There are two main cases to test here:
- * 1. In the first 3, the input value (or some combination of it) is combined
- * with & 0xF, which results in a maximum value of 0xF no matter what the
- * input value was. That is 15, which is always in range for the target span.
- * As a result, no input validation is needed at all in this case.
- * 2. There are two cases where the input value might cause an invalid access:
- * when it is either negative, or greater than 15 << 12. We can test both
- * conditions in a single pass by casting the input value to uint and right
- * shifting it by 12, which also preserves the sign. If it is a negative
- * value (2-complement), the test will fail as the uint cast will result
- * in a much larger value. If the value was simply too high, the test will
- * fail as expected. We can't simply check whether the value is lower than
- * 15 << 12, because higher values are acceptable in the first 3 accesses.
- * Doing this reduces the total number of index checks from 4 down to just 1. */
- int toReverseRightShiftBy12 = toReverse >> 12;
- Guard.MustBeLessThanOrEqualTo((uint)toReverseRightShiftBy12, 15, nameof(toReverse));
-
- ref byte bit4ReverseRef = ref MemoryMarshal.GetReference(Bit4Reverse);
-
- return (short)((Unsafe.Add(ref bit4ReverseRef, (uint)toReverse & 0xF) << 12)
- | (Unsafe.Add(ref bit4ReverseRef, (uint)(toReverse >> 4) & 0xF) << 8)
- | (Unsafe.Add(ref bit4ReverseRef, (uint)(toReverse >> 8) & 0xF) << 4)
- | Unsafe.Add(ref bit4ReverseRef, (uint)toReverseRightShiftBy12));
- }
-
- ///
- public void Dispose()
- {
- if (!this.isDisposed)
- {
- this.Pending.Dispose();
- this.distanceBufferHandle.Dispose();
- this.distanceMemoryOwner.Dispose();
- this.literalBufferHandle.Dispose();
- this.literalMemoryOwner.Dispose();
-
- this.literalTree.Dispose();
- this.blTree.Dispose();
- this.distTree.Dispose();
-
- this.isDisposed = true;
- }
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private static int Lcode(int length)
- {
- if (length == 255)
- {
- return 285;
- }
-
- int code = 257;
- while (length >= 8)
- {
- code += 4;
- length >>= 1;
- }
-
- return code + length;
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- private static int Dcode(int distance)
- {
- int code = 0;
- while (distance >= 4)
- {
- code += 2;
- distance >>= 1;
- }
-
- return code + distance;
- }
-
- private sealed class Tree : IDisposable
- {
- private readonly int minNumCodes;
- private readonly int[] bitLengthCounts;
- private readonly int maxLength;
- private bool isDisposed;
-
- private readonly int elementCount;
-
- private readonly MemoryAllocator memoryAllocator;
-
- private IMemoryOwner codesMemoryOwner;
- private MemoryHandle codesMemoryHandle;
- private readonly short* codes;
-
- private IMemoryOwner frequenciesMemoryOwner;
- private MemoryHandle frequenciesMemoryHandle;
-
- private IMemoryOwner lengthsMemoryOwner;
- private MemoryHandle lengthsMemoryHandle;
-
- public Tree(MemoryAllocator memoryAllocator, int elements, int minCodes, int maxLength)
- {
- this.memoryAllocator = memoryAllocator;
- this.elementCount = elements;
- this.minNumCodes = minCodes;
- this.maxLength = maxLength;
-
- this.frequenciesMemoryOwner = memoryAllocator.Allocate(elements);
- this.frequenciesMemoryHandle = this.frequenciesMemoryOwner.Memory.Pin();
- this.Frequencies = (short*)this.frequenciesMemoryHandle.Pointer;
-
- this.lengthsMemoryOwner = memoryAllocator.Allocate(elements);
- this.lengthsMemoryHandle = this.lengthsMemoryOwner.Memory.Pin();
- this.Length = (byte*)this.lengthsMemoryHandle.Pointer;
-
- this.codesMemoryOwner = memoryAllocator.Allocate(elements);
- this.codesMemoryHandle = this.codesMemoryOwner.Memory.Pin();
- this.codes = (short*)this.codesMemoryHandle.Pointer;
-
- // Maxes out at 15.
- this.bitLengthCounts = new int[maxLength];
- }
-
- public int NumCodes { get; private set; }
-
- public short* Frequencies { get; }
-
- public byte* Length { get; }
-
- ///
- /// Resets the internal state of the tree
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Reset()
- {
- this.frequenciesMemoryOwner.Memory.Span.Clear();
- this.lengthsMemoryOwner.Memory.Span.Clear();
- this.codesMemoryOwner.Memory.Span.Clear();
- }
-
- [MethodImpl(InliningOptions.ShortMethod)]
- public void WriteSymbol(DeflaterPendingBuffer pendingBuffer, int code)
- => pendingBuffer.WriteBits(this.codes[code] & 0xFFFF, this.Length[code]);
-
- ///
- /// Set static codes and length
- ///
- /// new codes
- /// length for new codes
- [MethodImpl(InliningOptions.ShortMethod)]
- public void SetStaticCodes(ReadOnlySpan staticCodes, ReadOnlySpan staticLengths)
- {
- staticCodes.CopyTo(this.codesMemoryOwner.Memory.Span);
- staticLengths.CopyTo(this.lengthsMemoryOwner.Memory.Span);
- }
-
- ///
- /// Build dynamic codes and lengths
- ///
- public void BuildCodes()
- {
- // Maxes out at 15 * 4
- Span nextCode = stackalloc int[this.maxLength];
- ref int nextCodeRef = ref MemoryMarshal.GetReference(nextCode);
- ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts);
-
- int code = 0;
- for (int bits = 0; bits < this.maxLength; bits++)
- {
- Unsafe.Add(ref nextCodeRef, (uint)bits) = code;
- code += Unsafe.Add(ref bitLengthCountsRef, (uint)bits) << (15 - bits);
- }
-
- for (int i = 0; i < this.NumCodes; i++)
- {
- int bits = this.Length[i];
- if (bits > 0)
- {
- this.codes[i] = BitReverse(Unsafe.Add(ref nextCodeRef, (uint)(bits - 1)));
- Unsafe.Add(ref nextCodeRef, (uint)(bits - 1)) += 1 << (16 - bits);
- }
- }
- }
-
- [MethodImpl(InliningOptions.HotPath)]
- public void BuildTree()
- {
- int numSymbols = this.elementCount;
-
- // heap is a priority queue, sorted by frequency, least frequent
- // nodes first. The heap is a binary tree, with the property, that
- // the parent node is smaller than both child nodes. This assures
- // that the smallest node is the first parent.
- //
- // The binary tree is encoded in an array: 0 is root node and
- // the nodes 2*n+1, 2*n+2 are the child nodes of node n.
- // Maxes out at 286 * 4 so too large for the stack.
- using (IMemoryOwner heapMemoryOwner = this.memoryAllocator.Allocate(numSymbols))
- {
- ref int heapRef = ref MemoryMarshal.GetReference(heapMemoryOwner.Memory.Span);
-
- int heapLen = 0;
- int maxCode = 0;
- for (int n = 0; n < numSymbols; n++)
- {
- int freq = this.Frequencies[n];
- if (freq != 0)
- {
- // Insert n into heap
- int pos = heapLen++;
- int ppos;
- while (pos > 0 && this.Frequencies[Unsafe.Add(ref heapRef, (uint)(ppos = (pos - 1) >> 1))] > freq)
- {
- Unsafe.Add(ref heapRef, pos) = Unsafe.Add(ref heapRef, (uint)ppos);
- pos = ppos;
- }
-
- Unsafe.Add(ref heapRef, (uint)pos) = n;
-
- maxCode = n;
- }
- }
-
- // We could encode a single literal with 0 bits but then we
- // don't see the literals. Therefore we force at least two
- // literals to avoid this case. We don't care about order in
- // this case, both literals get a 1 bit code.
- while (heapLen < 2)
- {
- Unsafe.Add(ref heapRef, (uint)heapLen++) = maxCode < 2 ? ++maxCode : 0;
- }
-
- this.NumCodes = Math.Max(maxCode + 1, this.minNumCodes);
-
- int numLeafs = heapLen;
- int childrenLength = (4 * heapLen) - 2;
- using (IMemoryOwner childrenMemoryOwner = this.memoryAllocator.Allocate(childrenLength))
- using (IMemoryOwner valuesMemoryOwner = this.memoryAllocator.Allocate((2 * heapLen) - 1))
- {
- ref int childrenRef = ref MemoryMarshal.GetReference(childrenMemoryOwner.Memory.Span);
- ref int valuesRef = ref MemoryMarshal.GetReference(valuesMemoryOwner.Memory.Span);
- int numNodes = numLeafs;
-
- for (nuint i = 0; i < (uint)heapLen; i++)
- {
- int node = Unsafe.Add(ref heapRef, i);
- nuint i2 = 2 * i;
- Unsafe.Add(ref childrenRef, i2) = node;
- Unsafe.Add(ref childrenRef, i2 + 1) = -1;
- Unsafe.Add(ref valuesRef, i) = this.Frequencies[node] << 8;
- Unsafe.Add(ref heapRef, i) = (int)i;
- }
-
- // Construct the Huffman tree by repeatedly combining the least two
- // frequent nodes.
- do
- {
- int first = Unsafe.Add(ref heapRef, 0);
- int last = Unsafe.Add(ref heapRef, (uint)--heapLen);
-
- // Propagate the hole to the leafs of the heap
- int ppos = 0;
- int path = 1;
-
- while (path < heapLen)
- {
- if (path + 1 < heapLen && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)path)) > Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(path + 1))))
- {
- path++;
- }
-
- Unsafe.Add(ref heapRef, (uint)ppos) = Unsafe.Add(ref heapRef, (uint)path);
- ppos = path;
- path = (path * 2) + 1;
- }
-
- // Now propagate the last element down along path. Normally
- // it shouldn't go too deep.
- int lastVal = Unsafe.Add(ref valuesRef, (uint)last);
- while ((path = ppos) > 0
- && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(ppos = (path - 1) >> 1))) > lastVal)
- {
- Unsafe.Add(ref heapRef, (uint)path) = Unsafe.Add(ref heapRef, (uint)ppos);
- }
-
- Unsafe.Add(ref heapRef, (uint)path) = last;
-
- int second = Unsafe.Add(ref heapRef, 0);
-
- // Create a new node father of first and second
- last = numNodes++;
- Unsafe.Add(ref childrenRef, (uint)(2 * last)) = first;
- Unsafe.Add(ref childrenRef, (uint)((2 * last) + 1)) = second;
- int mindepth = Math.Min(Unsafe.Add(ref valuesRef, (uint)first) & 0xFF, Unsafe.Add(ref valuesRef, (uint)second) & 0xFF);
- Unsafe.Add(ref valuesRef, (uint)last) = lastVal = Unsafe.Add(ref valuesRef, (uint)first) + Unsafe.Add(ref valuesRef, (uint)second) - mindepth + 1;
-
- // Again, propagate the hole to the leafs
- ppos = 0;
- path = 1;
-
- while (path < heapLen)
- {
- if (path + 1 < heapLen
- && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)path)) > Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(path + 1))))
- {
- path++;
- }
-
- Unsafe.Add(ref heapRef, (uint)ppos) = Unsafe.Add(ref heapRef, (uint)path);
- ppos = path;
- path = (ppos * 2) + 1;
- }
-
- // Now propagate the new element down along path
- while ((path = ppos) > 0 && Unsafe.Add(ref valuesRef, (uint)Unsafe.Add(ref heapRef, (uint)(ppos = (path - 1) >> 1))) > lastVal)
- {
- Unsafe.Add(ref heapRef, (uint)path) = Unsafe.Add(ref heapRef, (uint)ppos);
- }
-
- Unsafe.Add(ref heapRef, (uint)path) = last;
- }
- while (heapLen > 1);
-
- if (Unsafe.Add(ref heapRef, 0) != (childrenLength >> 1) - 1)
- {
- DeflateThrowHelper.ThrowHeapViolated();
- }
-
- this.BuildLength(childrenMemoryOwner.Memory.Span);
- }
- }
- }
-
- ///
- /// Get encoded length
- ///
- /// Encoded length, the sum of frequencies * lengths
- [MethodImpl(InliningOptions.ShortMethod)]
- public int GetEncodedLength()
- {
- int len = 0;
- for (int i = 0; i < this.elementCount; i++)
- {
- len += this.Frequencies[i] * this.Length[i];
- }
-
- return len;
- }
-
- ///
- /// Scan a literal or distance tree to determine the frequencies of the codes
- /// in the bit length tree.
- ///
- public void CalcBLFreq(Tree blTree)
- {
- int maxCount; // max repeat count
- int minCount; // min repeat count
- int count; // repeat count of the current code
- int curLen = -1; // length of current code
-
- int i = 0;
- while (i < this.NumCodes)
- {
- count = 1;
- int nextlen = this.Length[i];
- if (nextlen == 0)
- {
- maxCount = 138;
- minCount = 3;
- }
- else
- {
- maxCount = 6;
- minCount = 3;
- if (curLen != nextlen)
- {
- blTree.Frequencies[nextlen]++;
- count = 0;
- }
- }
-
- curLen = nextlen;
- i++;
-
- while (i < this.NumCodes && curLen == this.Length[i])
- {
- i++;
- if (++count >= maxCount)
- {
- break;
- }
- }
-
- if (count < minCount)
- {
- blTree.Frequencies[curLen] += (short)count;
- }
- else if (curLen != 0)
- {
- blTree.Frequencies[Repeat3To6]++;
- }
- else if (count <= 10)
- {
- blTree.Frequencies[Repeat3To10]++;
- }
- else
- {
- blTree.Frequencies[Repeat11To138]++;
- }
- }
- }
-
- ///
- /// Write the tree values.
- ///
- /// The pending buffer.
- /// The tree to write.
- public void WriteTree(DeflaterPendingBuffer pendingBuffer, Tree bitLengthTree)
- {
- int maxCount; // max repeat count
- int minCount; // min repeat count
- int count; // repeat count of the current code
- int curLen = -1; // length of current code
-
- int i = 0;
- while (i < this.NumCodes)
- {
- count = 1;
- int nextlen = this.Length[i];
- if (nextlen == 0)
- {
- maxCount = 138;
- minCount = 3;
- }
- else
- {
- maxCount = 6;
- minCount = 3;
- if (curLen != nextlen)
- {
- bitLengthTree.WriteSymbol(pendingBuffer, nextlen);
- count = 0;
- }
- }
-
- curLen = nextlen;
- i++;
-
- while (i < this.NumCodes && curLen == this.Length[i])
- {
- i++;
- if (++count >= maxCount)
- {
- break;
- }
- }
-
- if (count < minCount)
- {
- while (count-- > 0)
- {
- bitLengthTree.WriteSymbol(pendingBuffer, curLen);
- }
- }
- else if (curLen != 0)
- {
- bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To6);
- pendingBuffer.WriteBits(count - 3, 2);
- }
- else if (count <= 10)
- {
- bitLengthTree.WriteSymbol(pendingBuffer, Repeat3To10);
- pendingBuffer.WriteBits(count - 3, 3);
- }
- else
- {
- bitLengthTree.WriteSymbol(pendingBuffer, Repeat11To138);
- pendingBuffer.WriteBits(count - 11, 7);
- }
- }
- }
-
- private void BuildLength(ReadOnlySpan children)
- {
- byte* lengthPtr = this.Length;
- ref int childrenRef = ref MemoryMarshal.GetReference(children);
- ref int bitLengthCountsRef = ref MemoryMarshal.GetReference(this.bitLengthCounts);
-
- int maxLen = this.maxLength;
- int numNodes = children.Length >> 1;
- int numLeafs = (numNodes + 1) >> 1;
- int overflow = 0;
-
- Array.Clear(this.bitLengthCounts, 0, maxLen);
-
- // First calculate optimal bit lengths
- using (IMemoryOwner lengthsMemoryOwner = this.memoryAllocator.Allocate(numNodes, AllocationOptions.Clean))
- {
- ref int lengthsRef = ref MemoryMarshal.GetReference(lengthsMemoryOwner.Memory.Span);
-
- for (int i = numNodes - 1; i >= 0; i--)
- {
- if (children[(2 * i) + 1] != -1)
- {
- int bitLength = Unsafe.Add(ref lengthsRef, (uint)i) + 1;
- if (bitLength > maxLen)
- {
- bitLength = maxLen;
- overflow++;
- }
-
- Unsafe.Add(ref lengthsRef, (uint)Unsafe.Add(ref childrenRef, (uint)(2 * i))) = Unsafe.Add(ref lengthsRef, (uint)Unsafe.Add(ref childrenRef, (uint)((2 * i) + 1))) = bitLength;
- }
- else
- {
- // A leaf node
- int bitLength = Unsafe.Add(ref lengthsRef, (uint)i);
- Unsafe.Add(ref bitLengthCountsRef, (uint)(bitLength - 1))++;
- lengthPtr[Unsafe.Add(ref childrenRef, (uint)(2 * i))] = (byte)Unsafe.Add(ref lengthsRef, (uint)i);
- }
- }
- }
-
- if (overflow == 0)
- {
- return;
- }
-
- int incrBitLen = maxLen - 1;
- do
- {
- // Find the first bit length which could increase:
- while (Unsafe.Add(ref bitLengthCountsRef, (uint)--incrBitLen) == 0)
- {
- }
-
- // Move this node one down and remove a corresponding
- // number of overflow nodes.
- do
- {
- Unsafe.Add(ref bitLengthCountsRef, (uint)incrBitLen)--;
- Unsafe.Add(ref bitLengthCountsRef, (uint)++incrBitLen)++;
- overflow -= 1 << (maxLen - 1 - incrBitLen);
- }
- while (overflow > 0 && incrBitLen < maxLen - 1);
- }
- while (overflow > 0);
-
- // We may have overshot above. Move some nodes from maxLength to
- // maxLength-1 in that case.
- Unsafe.Add(ref bitLengthCountsRef, (uint)(maxLen - 1)) += overflow;
- Unsafe.Add(ref bitLengthCountsRef, (uint)(maxLen - 2)) -= overflow;
-
- // Now recompute all bit lengths, scanning in increasing
- // frequency. It is simpler to reconstruct all lengths instead of
- // fixing only the wrong ones. This idea is taken from 'ar'
- // written by Haruhiko Okumura.
- //
- // The nodes were inserted with decreasing frequency into the childs
- // array.
- int nodeIndex = 2 * numLeafs;
- for (int bits = maxLen; bits != 0; bits--)
- {
- int n = Unsafe.Add(ref bitLengthCountsRef, (uint)(bits - 1));
- while (n > 0)
- {
- int childIndex = 2 * Unsafe.Add(ref childrenRef, (uint)nodeIndex++);
- if (Unsafe.Add(ref childrenRef, (uint)(childIndex + 1)) == -1)
- {
- // We found another leaf
- lengthPtr[Unsafe.Add(ref childrenRef, (uint)childIndex)] = (byte)bits;
- n--;
- }
- }
- }
- }
-
- public void Dispose()
- {
- if (!this.isDisposed)
- {
- this.frequenciesMemoryHandle.Dispose();
- this.frequenciesMemoryOwner.Dispose();
-
- this.lengthsMemoryHandle.Dispose();
- this.lengthsMemoryOwner.Dispose();
-
- this.codesMemoryHandle.Dispose();
- this.codesMemoryOwner.Dispose();
-
- this.isDisposed = true;
- }
- }
- }
-}
diff --git a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs b/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
deleted file mode 100644
index de818fd8f..000000000
--- a/src/ImageSharp/Compression/Zlib/DeflaterOutputStream.cs
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Buffers;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// A special stream deflating or compressing the bytes that are
-/// written to it. It uses a Deflater to perform actual deflating.
-///
-internal sealed class DeflaterOutputStream : Stream
-{
- private const int BufferLength = 512;
- private IMemoryOwner memoryOwner;
- private readonly Memory buffer;
- private Deflater deflater;
- private readonly Stream rawStream;
- private bool isDisposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- /// The output stream where deflated output is written.
- /// The compression level.
- public DeflaterOutputStream(MemoryAllocator memoryAllocator, Stream rawStream, int compressionLevel)
- {
- this.rawStream = rawStream;
- this.memoryOwner = memoryAllocator.Allocate(BufferLength);
- this.buffer = this.memoryOwner.Memory;
- this.deflater = new Deflater(memoryAllocator, compressionLevel);
- }
-
- ///
- public override bool CanRead => false;
-
- ///
- public override bool CanSeek => false;
-
- ///
- public override bool CanWrite => this.rawStream.CanWrite;
-
- ///
- public override long Length => this.rawStream.Length;
-
- ///
- public override long Position
- {
- get => this.rawStream.Position;
-
- set => throw new NotSupportedException();
- }
-
- ///
- public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
-
- ///
- public override void SetLength(long value) => throw new NotSupportedException();
-
- ///
- public override int ReadByte() => throw new NotSupportedException();
-
- ///
- public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
-
- ///
- public override void Flush()
- {
- this.deflater.Flush();
- this.Deflate(true);
- this.rawStream.Flush();
- }
-
- ///
- public override void Write(byte[] buffer, int offset, int count)
- {
- this.deflater.SetInput(buffer, offset, count);
- this.Deflate();
- }
-
- private void Deflate() => this.Deflate(false);
-
- private void Deflate(bool flushing)
- {
- while (flushing || !this.deflater.IsNeedingInput)
- {
- int deflateCount = this.deflater.Deflate(this.buffer.Span, 0, BufferLength);
-
- if (deflateCount <= 0)
- {
- break;
- }
-
- this.rawStream.Write(this.buffer.Span[..deflateCount]);
- }
-
- if (!this.deflater.IsNeedingInput)
- {
- DeflateThrowHelper.ThrowNoDeflate();
- }
- }
-
- private void Finish()
- {
- this.deflater.Finish();
- while (!this.deflater.IsFinished)
- {
- int len = this.deflater.Deflate(this.buffer.Span, 0, BufferLength);
- if (len <= 0)
- {
- break;
- }
-
- this.rawStream.Write(this.buffer.Span[..len]);
- }
-
- if (!this.deflater.IsFinished)
- {
- DeflateThrowHelper.ThrowNoDeflate();
- }
-
- this.rawStream.Flush();
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (!this.isDisposed)
- {
- if (disposing)
- {
- this.Finish();
- this.deflater.Dispose();
- this.memoryOwner.Dispose();
- }
-
- this.isDisposed = true;
- base.Dispose(disposing);
- }
- }
-}
diff --git a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs b/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
deleted file mode 100644
index 37e7404e4..000000000
--- a/src/ImageSharp/Compression/Zlib/DeflaterPendingBuffer.cs
+++ /dev/null
@@ -1,185 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Buffers;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// Stores pending data for writing data to the Deflater.
-///
-internal sealed unsafe class DeflaterPendingBuffer : IDisposable
-{
- private readonly Memory buffer;
- private readonly byte* pinnedBuffer;
- private IMemoryOwner bufferMemoryOwner;
- private MemoryHandle bufferMemoryHandle;
-
- private int start;
- private int end;
- private uint bits;
- private bool isDisposed;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- public DeflaterPendingBuffer(MemoryAllocator memoryAllocator)
- {
- this.bufferMemoryOwner = memoryAllocator.Allocate(DeflaterConstants.PENDING_BUF_SIZE);
- this.buffer = this.bufferMemoryOwner.Memory;
- this.bufferMemoryHandle = this.buffer.Pin();
- this.pinnedBuffer = (byte*)this.bufferMemoryHandle.Pointer;
- }
-
- ///
- /// Gets the number of bits written to the buffer.
- ///
- public int BitCount { get; private set; }
-
- ///
- /// Gets a value indicating whether indicates the buffer has been flushed.
- ///
- public bool IsFlushed => this.end == 0;
-
- ///
- /// Clear internal state/buffers.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void Reset() => this.start = this.end = this.BitCount = 0;
-
- ///
- /// Write a short value to buffer LSB first.
- ///
- /// The value to write.
- [MethodImpl(InliningOptions.ShortMethod)]
- public void WriteShort(int value)
- {
- byte* pinned = this.pinnedBuffer;
- pinned[this.end++] = unchecked((byte)value);
- pinned[this.end++] = unchecked((byte)(value >> 8));
- }
-
- ///
- /// Write a block of data to the internal buffer.
- ///
- /// The data to write.
- /// The offset of first byte to write.
- /// The number of bytes to write.
- [MethodImpl(InliningOptions.ShortMethod)]
- public void WriteBlock(ReadOnlySpan block, int offset, int length)
- {
- Unsafe.CopyBlockUnaligned(
- ref this.buffer.Span[this.end],
- ref MemoryMarshal.GetReference(block[offset..]),
- unchecked((uint)length));
-
- this.end += length;
- }
-
- ///
- /// Aligns internal buffer on a byte boundary.
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public void AlignToByte()
- {
- if (this.BitCount > 0)
- {
- byte* pinned = this.pinnedBuffer;
- pinned[this.end++] = unchecked((byte)this.bits);
- if (this.BitCount > 8)
- {
- pinned[this.end++] = unchecked((byte)(this.bits >> 8));
- }
- }
-
- this.bits = 0;
- this.BitCount = 0;
- }
-
- ///
- /// Write bits to internal buffer
- ///
- /// source of bits
- /// number of bits to write
- [MethodImpl(InliningOptions.ShortMethod)]
- public void WriteBits(int b, int count)
- {
- this.bits |= (uint)(b << this.BitCount);
- this.BitCount += count;
- if (this.BitCount >= 16)
- {
- byte* pinned = this.pinnedBuffer;
- pinned[this.end++] = unchecked((byte)this.bits);
- pinned[this.end++] = unchecked((byte)(this.bits >> 8));
- this.bits >>= 16;
- this.BitCount -= 16;
- }
- }
-
- ///
- /// Write a short value to internal buffer most significant byte first
- ///
- /// The value to write
- [MethodImpl(InliningOptions.ShortMethod)]
- public void WriteShortMSB(int value)
- {
- byte* pinned = this.pinnedBuffer;
- pinned[this.end++] = unchecked((byte)(value >> 8));
- pinned[this.end++] = unchecked((byte)value);
- }
-
- ///
- /// Flushes the pending buffer into the given output array.
- /// If the output array is to small, only a partial flush is done.
- ///
- /// The output array.
- /// The offset into output array.
- /// The maximum number of bytes to store.
- /// The number of bytes flushed.
- public int Flush(Span output, int offset, int length)
- {
- if (this.BitCount >= 8)
- {
- this.pinnedBuffer[this.end++] = unchecked((byte)this.bits);
- this.bits >>= 8;
- this.BitCount -= 8;
- }
-
- if (length > this.end - this.start)
- {
- length = this.end - this.start;
-
- Unsafe.CopyBlockUnaligned(
- ref output[offset],
- ref this.buffer.Span[this.start],
- unchecked((uint)length));
- this.start = 0;
- this.end = 0;
- }
- else
- {
- Unsafe.CopyBlockUnaligned(
- ref output[offset],
- ref this.buffer.Span[this.start],
- unchecked((uint)length));
- this.start += length;
- }
-
- return length;
- }
-
- ///
- public void Dispose()
- {
- if (!this.isDisposed)
- {
- this.bufferMemoryHandle.Dispose();
- this.bufferMemoryOwner.Dispose();
- this.isDisposed = true;
- }
- }
-}
diff --git a/src/ImageSharp/Compression/Zlib/README.md b/src/ImageSharp/Compression/Zlib/README.md
deleted file mode 100644
index 3875f9884..000000000
--- a/src/ImageSharp/Compression/Zlib/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-DeflateStream implementation adapted from
-
-https://github.com/icsharpcode/SharpZipLib
-
-Licensed under MIT
-
-Crc32 and Adler32 SIMD implementation adapted from
-
-https://github.com/chromium/chromium
-
-Licensed under BSD 3-Clause "New" or "Revised" License
diff --git a/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs b/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
deleted file mode 100644
index 2e52f84d7..000000000
--- a/src/ImageSharp/Compression/Zlib/ZlibDeflateStream.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using System.Runtime.CompilerServices;
-using SixLabors.ImageSharp.Formats.Png;
-using SixLabors.ImageSharp.Memory;
-
-namespace SixLabors.ImageSharp.Compression.Zlib;
-
-///
-/// Provides methods and properties for compressing streams by using the Zlib Deflate algorithm.
-///
-internal sealed class ZlibDeflateStream : Stream
-{
- ///
- /// The raw stream containing the uncompressed image data.
- ///
- private readonly Stream rawStream;
-
- ///
- /// Computes the checksum for the data stream.
- ///
- private uint adler = Adler32.SeedValue;
-
- ///
- /// 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;
-
- ///
- /// The stream responsible for compressing the input stream.
- ///
- private DeflaterOutputStream deflateStream;
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- /// The stream to compress.
- /// The compression level.
- public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, DeflateCompressionLevel level)
- : this(memoryAllocator, stream, (PngCompressionLevel)level)
- {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The memory allocator to use for buffer allocations.
- /// The stream to compress.
- /// The compression level.
- public ZlibDeflateStream(MemoryAllocator memoryAllocator, Stream stream, PngCompressionLevel level)
- {
- int compressionLevel = (int)level;
- this.rawStream = stream;
-
- // Write the zlib header : http://tools.ietf.org/html/rfc1950
- // CMF(Compression Method and flags)
- // This byte is divided into a 4 - bit compression method and a
- // 4-bit information field depending on the compression method.
- // bits 0 to 3 CM Compression method
- // bits 4 to 7 CINFO Compression info
- //
- // 0 1
- // +---+---+
- // |CMF|FLG|
- // +---+---+
- const int Cmf = 0x78;
- int flg = 218;
-
- // http://stackoverflow.com/a/2331025/277304
- if (compressionLevel >= 5 && compressionLevel <= 6)
- {
- flg = 156;
- }
- else if (compressionLevel >= 3 && compressionLevel <= 4)
- {
- flg = 94;
- }
- else if (compressionLevel <= 2)
- {
- flg = 1;
- }
-
- // Just in case
- flg -= ((Cmf * 256) + flg) % 31;
-
- if (flg < 0)
- {
- flg += 31;
- }
-
- this.rawStream.WriteByte(Cmf);
- this.rawStream.WriteByte((byte)flg);
-
- this.deflateStream = new DeflaterOutputStream(memoryAllocator, this.rawStream, compressionLevel);
- }
-
- ///
- public override bool CanRead => false;
-
- ///
- public override bool CanSeek => false;
-
- ///
- public override bool CanWrite => this.rawStream.CanWrite;
-
- ///
- public override long Length => this.rawStream.Length;
-
- ///
- public override long Position
- {
- get
- {
- return this.rawStream.Position;
- }
-
- set
- {
- throw new NotSupportedException();
- }
- }
-
- ///
- public override void Flush() => this.deflateStream.Flush();
-
- ///
- public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException();
-
- ///
- public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
-
- ///
- public override void SetLength(long value) => throw new NotSupportedException();
-
- ///
- [MethodImpl(InliningOptions.ShortMethod)]
- public override void Write(byte[] buffer, int offset, int count)
- {
- this.deflateStream.Write(buffer, offset, count);
- this.adler = Adler32.Calculate(this.adler, buffer.AsSpan(offset, count));
- }
-
- ///
- protected override void Dispose(bool disposing)
- {
- if (this.isDisposed)
- {
- return;
- }
-
- if (disposing)
- {
- // dispose managed resources
- this.deflateStream.Dispose();
-
- // Add the crc
- uint crc = this.adler;
- this.rawStream.WriteByte((byte)((crc >> 24) & 0xFF));
- this.rawStream.WriteByte((byte)((crc >> 16) & 0xFF));
- this.rawStream.WriteByte((byte)((crc >> 8) & 0xFF));
- this.rawStream.WriteByte((byte)(crc & 0xFF));
- }
-
- base.Dispose(disposing);
- this.isDisposed = true;
- }
-}
diff --git a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
index 01248cc39..f9b68e5b8 100644
--- a/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
+++ b/src/ImageSharp/Formats/Exr/Compression/Compressors/ZipExrCompressor.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Memory;
@@ -13,8 +14,6 @@ internal class ZipExrCompressor : ExrBaseCompressor
{
private readonly DeflateCompressionLevel compressionLevel;
- private readonly MemoryStream memoryStream;
-
private readonly System.Buffers.IMemoryOwner buffer;
///
@@ -32,7 +31,6 @@ internal class ZipExrCompressor : ExrBaseCompressor
{
this.compressionLevel = compressionLevel;
this.buffer = allocator.Allocate((int)bytesPerBlock);
- this.memoryStream = new();
}
///
@@ -59,28 +57,22 @@ internal class ZipExrCompressor : ExrBaseCompressor
predicted[i] = (byte)d;
}
- this.memoryStream.Seek(0, SeekOrigin.Begin);
- using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel))
+ // Compressed bytes stream straight to the output in fixed segments. The block size is
+ // totaled in the callback because the final partial segment is only emitted on disposal.
+ uint size = 0;
+ using (ChunkedWriteStream segmentStream = new(this.Allocator, segment =>
+ {
+ this.Output.Write(segment);
+ size += (uint)segment.Length;
+ }))
+ using (ZLibStream stream = new(segmentStream, new ZLibCompressionOptions { CompressionLevel = (int)this.compressionLevel }, true))
{
stream.Write(predicted);
- stream.Flush();
}
- int size = (int)this.memoryStream.Position;
- byte[] buffer = this.memoryStream.GetBuffer();
- this.Output.Write(buffer, 0, size);
-
- // Reset memory stream for next pixel row.
- this.memoryStream.Seek(0, SeekOrigin.Begin);
- this.memoryStream.SetLength(0);
-
- return (uint)size;
+ return size;
}
///
- protected override void Dispose(bool disposing)
- {
- this.buffer.Dispose();
- this.memoryStream?.Dispose();
- }
+ protected override void Dispose(bool disposing) => this.buffer.Dispose();
}
diff --git a/src/ImageSharp/Formats/Png/PngEncoderCore.cs b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
index 1f5bbc41c..caf2393d8 100644
--- a/src/ImageSharp/Formats/Png/PngEncoderCore.cs
+++ b/src/ImageSharp/Formats/Png/PngEncoderCore.cs
@@ -4,6 +4,7 @@
using System.Buffers;
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
+using System.IO.Compression;
using System.IO.Hashing;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -1197,7 +1198,7 @@ internal sealed class PngEncoderCore : IDisposable
private byte[] GetZlibCompressedBytes(byte[] dataBytes)
{
using MemoryStream memoryStream = new();
- using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.encoder.CompressionLevel))
+ using (ZLibStream deflateStream = new(memoryStream, new ZLibCompressionOptions { CompressionLevel = (int)this.encoder.CompressionLevel }, true))
{
deflateStream.Write(dataBytes);
}
@@ -1320,34 +1321,6 @@ internal sealed class PngEncoderCore : IDisposable
private uint WriteDataChunks(in FrameControl frameControl, in Buffer2DRegion frame, IndexedImageFrame? quantized, Stream stream, bool isFrame)
where TPixel : unmanaged, IPixel
{
- byte[] buffer;
- int bufferLength;
-
- using (MemoryStream memoryStream = new())
- {
- using (ZlibDeflateStream deflateStream = new(this.memoryAllocator, memoryStream, this.encoder.CompressionLevel))
- {
- if (this.interlaceMode is PngInterlaceMode.Adam7)
- {
- if (quantized is not null)
- {
- this.EncodeAdam7IndexedPixels(quantized, deflateStream);
- }
- else
- {
- this.EncodeAdam7Pixels(in frame, deflateStream);
- }
- }
- else
- {
- this.EncodePixels(in frame, quantized, deflateStream);
- }
- }
-
- buffer = memoryStream.ToArray();
- bufferLength = buffer.Length;
- }
-
// Store the chunks in repeated 64k blocks.
// This reduces the memory load for decoding the image for many decoders.
int maxBlockSize = MaxBlockSize;
@@ -1356,36 +1329,46 @@ internal sealed class PngEncoderCore : IDisposable
maxBlockSize -= 4;
}
- int numChunks = bufferLength / maxBlockSize;
-
- if (bufferLength % maxBlockSize != 0)
- {
- numChunks++;
- }
-
- for (int i = 0; i < numChunks; i++)
+ // Compressed bytes stream straight into data chunks as each block fills, so nothing
+ // larger than one block is buffered. The final partial block is emitted when the
+ // segment stream is disposed, after the deflate stream has written its trailer.
+ // '1' is added to the sequence number to account for the preceding frame control chunk;
+ // it then increments for each frame data chunk.
+ uint numChunks = 0;
+ uint sequenceNumber = frameControl.SequenceNumber + 1;
+ using (ChunkedWriteStream segmentStream = new(this.memoryAllocator, maxBlockSize, segment =>
{
- int length = bufferLength - (i * maxBlockSize);
-
- if (length > maxBlockSize)
+ if (isFrame)
+ {
+ this.WriteFrameDataChunk(stream, sequenceNumber++, segment, 0, segment.Length);
+ }
+ else
{
- length = maxBlockSize;
+ this.WriteChunk(stream, PngChunkType.Data, segment);
}
- if (isFrame)
+ numChunks++;
+ }))
+ using (ZLibStream deflateStream = new(segmentStream, new ZLibCompressionOptions { CompressionLevel = (int)this.encoder.CompressionLevel }, true))
+ {
+ if (this.interlaceMode is PngInterlaceMode.Adam7)
{
- // We increment the sequence number for each frame chunk.
- // '1' is added to the sequence number to account for the preceding frame control chunk.
- uint sequenceNumber = (uint)(frameControl.SequenceNumber + 1 + i);
- this.WriteFrameDataChunk(stream, sequenceNumber, buffer, i * maxBlockSize, length);
+ if (quantized is not null)
+ {
+ this.EncodeAdam7IndexedPixels(quantized, deflateStream);
+ }
+ else
+ {
+ this.EncodeAdam7Pixels(in frame, deflateStream);
+ }
}
else
{
- this.WriteChunk(stream, PngChunkType.Data, buffer, i * maxBlockSize, length);
+ this.EncodePixels(in frame, quantized, deflateStream);
}
}
- return (uint)numChunks;
+ return numChunks;
}
///
@@ -1408,7 +1391,7 @@ internal sealed class PngEncoderCore : IDisposable
/// The image frame pixel buffer.
/// The quantized pixels.
/// The deflate stream.
- private void EncodePixels(in Buffer2DRegion pixels, IndexedImageFrame? quantized, ZlibDeflateStream deflateStream)
+ private void EncodePixels(in Buffer2DRegion pixels, IndexedImageFrame? quantized, ZLibStream deflateStream)
where TPixel : unmanaged, IPixel
{
int bytesPerScanline = this.CalculateScanlineLength(pixels.Width);
@@ -1435,7 +1418,7 @@ internal sealed class PngEncoderCore : IDisposable
/// The type of the pixel.
/// The image frame pixel buffer.
/// The deflate stream.
- private void EncodeAdam7Pixels(in Buffer2DRegion pixels, ZlibDeflateStream deflateStream)
+ private void EncodeAdam7Pixels(in Buffer2DRegion pixels, ZLibStream deflateStream)
where TPixel : unmanaged, IPixel
{
for (int pass = 0; pass < 7; pass++)
@@ -1486,7 +1469,7 @@ internal sealed class PngEncoderCore : IDisposable
/// The type of the pixel.
/// The quantized.
/// The deflate stream.
- private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZlibDeflateStream deflateStream)
+ private void EncodeAdam7IndexedPixels(IndexedImageFrame quantized, ZLibStream deflateStream)
where TPixel : unmanaged, IPixel
{
for (int pass = 0; pass < 7; pass++)
diff --git a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
index 3debd373c..1f2ec2fe9 100644
--- a/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
+++ b/src/ImageSharp/Formats/Tiff/Compression/Compressors/DeflateCompressor.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
using SixLabors.ImageSharp.Memory;
@@ -11,8 +12,6 @@ internal sealed class DeflateCompressor : TiffBaseCompressor
{
private readonly DeflateCompressionLevel compressionLevel;
- private readonly MemoryStream memoryStream = new();
-
public DeflateCompressor(Stream output, MemoryAllocator allocator, int width, int bitsPerPixel, TiffPredictor predictor, DeflateCompressionLevel compressionLevel)
: base(output, allocator, width, bitsPerPixel, predictor)
=> this.compressionLevel = compressionLevel;
@@ -28,21 +27,16 @@ internal sealed class DeflateCompressor : TiffBaseCompressor
///
public override void CompressStrip(Span rows, int height)
{
- this.memoryStream.Seek(0, SeekOrigin.Begin);
- using (ZlibDeflateStream stream = new(this.Allocator, this.memoryStream, this.compressionLevel))
+ if (this.Predictor == TiffPredictor.Horizontal)
{
- if (this.Predictor == TiffPredictor.Horizontal)
- {
- HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
- }
-
- stream.Write(rows);
- stream.Flush();
+ HorizontalPredictor.ApplyHorizontalPrediction(rows, this.BytesPerRow, this.BitsPerPixel);
}
- int size = (int)this.memoryStream.Position;
- byte[] buffer = this.memoryStream.GetBuffer();
- this.Output.Write(buffer, 0, size);
+ // Compressed bytes stream straight to the output in fixed segments; the strip byte
+ // count is measured by the caller from the output position.
+ using ChunkedWriteStream segmentStream = new(this.Allocator, this.Output.Write);
+ using ZLibStream stream = new(segmentStream, new ZLibCompressionOptions { CompressionLevel = (int)this.compressionLevel }, true);
+ stream.Write(rows);
}
///
diff --git a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs b/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs
deleted file mode 100644
index 64a8092c6..000000000
--- a/tests/ImageSharp.Benchmarks/General/Adler32Benchmark.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using BenchmarkDotNet.Attributes;
-using SixLabors.ImageSharp.Compression.Zlib;
-using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
-
-namespace SixLabors.ImageSharp.Benchmarks.General;
-
-[Config(typeof(Config.Short))]
-public class Adler32Benchmark
-{
- private byte[] data;
- private readonly SharpAdler32 adler = new();
-
- [Params(1024, 2048, 4096)]
- public int Count { get; set; }
-
- [GlobalSetup]
- public void SetUp()
- {
- this.data = new byte[this.Count];
- new Random(1).NextBytes(this.data);
- }
-
- [Benchmark(Baseline = true)]
- public long SharpZipLibCalculate()
- {
- this.adler.Reset();
- this.adler.Update(this.data);
- return this.adler.Value;
- }
-
- [Benchmark]
- public uint SixLaborsCalculate()
- {
- return Adler32.Calculate(this.data);
- }
-}
-
-// ########## 17/05/2020 ##########
-//
-// | Method | Runtime | Count | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
-// |--------------------- |-------------- |------ |------------:|------------:|----------:|------:|--------:|------:|------:|------:|----------:|
-// | SharpZipLibCalculate | .NET 4.7.2 | 1024 | 793.18 ns | 775.66 ns | 42.516 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET 4.7.2 | 1024 | 384.86 ns | 15.64 ns | 0.857 ns | 0.49 | 0.03 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET Core 2.1 | 1024 | 790.31 ns | 353.34 ns | 19.368 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET Core 2.1 | 1024 | 465.28 ns | 652.41 ns | 35.761 ns | 0.59 | 0.03 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET Core 3.1 | 1024 | 877.25 ns | 97.89 ns | 5.365 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET Core 3.1 | 1024 | 45.60 ns | 13.28 ns | 0.728 ns | 0.05 | 0.00 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET 4.7.2 | 2048 | 1,537.04 ns | 428.44 ns | 23.484 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET 4.7.2 | 2048 | 849.76 ns | 1,066.34 ns | 58.450 ns | 0.55 | 0.04 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET Core 2.1 | 2048 | 1,616.97 ns | 276.70 ns | 15.167 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET Core 2.1 | 2048 | 790.77 ns | 691.71 ns | 37.915 ns | 0.49 | 0.03 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET Core 3.1 | 2048 | 1,735.11 ns | 1,374.22 ns | 75.325 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET Core 3.1 | 2048 | 87.80 ns | 56.84 ns | 3.116 ns | 0.05 | 0.00 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET 4.7.2 | 4096 | 3,054.53 ns | 796.41 ns | 43.654 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET 4.7.2 | 4096 | 1,538.90 ns | 487.02 ns | 26.695 ns | 0.50 | 0.01 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET Core 2.1 | 4096 | 3,223.48 ns | 32.32 ns | 1.771 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET Core 2.1 | 4096 | 1,547.60 ns | 309.72 ns | 16.977 ns | 0.48 | 0.01 | - | - | - | - |
-// | | | | | | | | | | | | |
-// | SharpZipLibCalculate | .NET Core 3.1 | 4096 | 3,672.33 ns | 1,095.81 ns | 60.065 ns | 1.00 | 0.00 | - | - | - | - |
-// | SixLaborsCalculate | .NET Core 3.1 | 4096 | 159.44 ns | 36.31 ns | 1.990 ns | 0.04 | 0.00 | - | - | - | - |
diff --git a/tests/ImageSharp.Tests/Compression/Zlib/ChunkedWriteStreamTests.cs b/tests/ImageSharp.Tests/Compression/Zlib/ChunkedWriteStreamTests.cs
new file mode 100644
index 000000000..09b7a46db
--- /dev/null
+++ b/tests/ImageSharp.Tests/Compression/Zlib/ChunkedWriteStreamTests.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Six Labors.
+// Licensed under the Six Labors Split License.
+
+using SixLabors.ImageSharp.Compression.Zlib;
+
+namespace SixLabors.ImageSharp.Tests.Compression.Zlib;
+
+public class ChunkedWriteStreamTests
+{
+ [Theory]
+ [InlineData(1)]
+ [InlineData(7)]
+ [InlineData(64)]
+ [InlineData(1000)]
+ public void Write_EmitsFixedLengthSegments_AndPartialTailOnDispose(int writeSize)
+ {
+ const int SegmentLength = 64;
+ byte[] data = new byte[250];
+ new Random(42).NextBytes(data);
+
+ List segments = [];
+ using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, SegmentLength, segment => segments.Add(segment.ToArray())))
+ {
+ for (int offset = 0; offset < data.Length; offset += writeSize)
+ {
+ stream.Write(data, offset, Math.Min(writeSize, data.Length - offset));
+ }
+
+ // Nothing but full segments is emitted before disposal.
+ Assert.Equal(3, segments.Count);
+ Assert.All(segments, s => Assert.Equal(SegmentLength, s.Length));
+ }
+
+ Assert.Equal(4, segments.Count);
+ Assert.Equal(250 - (3 * SegmentLength), segments[3].Length);
+ Assert.Equal(data, segments.SelectMany(s => s).ToArray());
+ }
+
+ [Fact]
+ public void Write_ExactMultipleOfSegmentLength_DoesNotEmitEmptyTail()
+ {
+ const int SegmentLength = 16;
+ byte[] data = new byte[SegmentLength * 3];
+
+ int count = 0;
+ using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, SegmentLength, _ => count++))
+ {
+ stream.Write(data);
+ }
+
+ Assert.Equal(3, count);
+ }
+
+ [Fact]
+ public void WriteByte_FillsSegments()
+ {
+ const int SegmentLength = 4;
+ List segments = [];
+ using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, SegmentLength, segment => segments.Add(segment.ToArray())))
+ {
+ for (byte i = 0; i < 6; i++)
+ {
+ stream.WriteByte(i);
+ }
+ }
+
+ Assert.Equal(2, segments.Count);
+ Assert.Equal(new byte[] { 0, 1, 2, 3 }, segments[0]);
+ Assert.Equal(new byte[] { 4, 5 }, segments[1]);
+ }
+
+ [Fact]
+ public void Flush_DoesNotEmitPartialSegment()
+ {
+ int count = 0;
+ using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, 16, _ => count++))
+ {
+ stream.Write(new byte[5]);
+ stream.Flush();
+ Assert.Equal(0, count);
+ }
+
+ Assert.Equal(1, count);
+ }
+
+ [Fact]
+ public void Dispose_WithoutWrites_EmitsNothing()
+ {
+ int count = 0;
+ using (ChunkedWriteStream stream = new(Configuration.Default.MemoryAllocator, 16, _ => count++))
+ {
+ }
+
+ Assert.Equal(0, count);
+ }
+}
diff --git a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs b/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
deleted file mode 100644
index c2b640a1d..000000000
--- a/tests/ImageSharp.Tests/Formats/Png/Adler32Tests.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-// Copyright (c) Six Labors.
-// Licensed under the Six Labors Split License.
-
-using SixLabors.ImageSharp.Compression.Zlib;
-using SixLabors.ImageSharp.Tests.TestUtilities;
-using SharpAdler32 = ICSharpCode.SharpZipLib.Checksum.Adler32;
-
-namespace SixLabors.ImageSharp.Tests.Formats.Png;
-
-[Trait("Format", "Png")]
-public class Adler32Tests
-{
- [Theory]
- [InlineData(0)]
- [InlineData(1)]
- [InlineData(2)]
- public void CalculateAdler_ReturnsCorrectWhenEmpty(uint input) => Assert.Equal(input, Adler32.Calculate(input, default));
-
- [Theory]
- [InlineData(0)]
- [InlineData(8)]
- [InlineData(215)]
- [InlineData(1024)]
- [InlineData(1024 + 15)]
- [InlineData(2034)]
- [InlineData(4096)]
- public void CalculateAdler_MatchesReference(int length) => CalculateAdlerAndCompareToReference(length);
-
- private static void CalculateAdlerAndCompareToReference(int length)
- {
- // arrange
- byte[] data = GetBuffer(length);
- SharpAdler32 adler = new();
- adler.Update(data);
- long expected = adler.Value;
-
- // act
- long actual = Adler32.Calculate(data);
-
- // assert
- Assert.Equal(expected, actual);
- }
-
- private static byte[] GetBuffer(int length)
- {
- byte[] data = new byte[length];
- new Random(1).NextBytes(data);
-
- return data;
- }
-
- [Fact]
- public void RunCalculateAdlerTest_WithHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll);
-
- [Fact]
- public void RunCalculateAdlerTest_WithAvxDisabled_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.AllowAll | HwIntrinsics.DisableAVX2);
-
- [Fact]
- public void RunCalculateAdlerTest_WithoutHardwareIntrinsics_Works() => FeatureTestRunner.RunWithHwIntrinsicsFeature(RunCalculateAdlerTest, HwIntrinsics.DisableHWIntrinsic);
-
- private static void RunCalculateAdlerTest()
- {
- int[] testData = [0, 8, 215, 1024, 1024 + 15, 2034, 4096];
- for (int i = 0; i < testData.Length; i++)
- {
- CalculateAdlerAndCompareToReference(testData[i]);
- }
- }
-}
diff --git a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
index cc2faeab7..f36bcd6bd 100644
--- a/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
+++ b/tests/ImageSharp.Tests/Formats/Tiff/Compression/DeflateTiffCompressionTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
+using System.IO.Compression;
using SixLabors.ImageSharp.Compression.Zlib;
using SixLabors.ImageSharp.Formats.Tiff.Compression.Decompressors;
using SixLabors.ImageSharp.Formats.Tiff.Constants;
@@ -35,7 +36,7 @@ public class DeflateTiffCompressionTests
Stream compressedStream = new MemoryStream();
using (Stream uncompressedStream = new MemoryStream(data),
- deflateStream = new ZlibDeflateStream(Configuration.Default.MemoryAllocator, compressedStream, DeflateCompressionLevel.Level6))
+ deflateStream = new ZLibStream(compressedStream, new ZLibCompressionOptions { CompressionLevel = (int)DeflateCompressionLevel.Level6 }, true))
{
uncompressedStream.CopyTo(deflateStream);
}