-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Description
Background and motivation
Currently there is no non-stream based apis to zlib compression. As such I feel like an encoder / decoder implementation is needed similar to the Brotli implementation.
The brotli implementation also uses the encoder / decoder internally in the streams, which can help make the implementations of the zlib based streams (GZipStream, DeflateStream, and ZLibStream) better.
A single ZlibEncoder and ZlibDecoder that takes a class of values (ZlibOptions), where ZlibOptions also has subclasses named DeflateOptions, and GZipOptions where only the window bits are different.
This issue partially addresses:
- Standardize pattern for exposing advanced configuration for compression streams #42820
- prerequisite of this is: Standardize pattern for exposing advanced configuration for compression streams #42820 (comment) (Comment to the above issue)
Implementing this issue should also resolve this one as well:
I currently have a baseline implementation locally of this (except for the stream changes that would need to be done), and it should be ready by the time .NET 7 goes into an api freeze (until .NET 8's development starts).
API Proposal ( updated by @iremyux )
namespace System.IO.Compression
{
public sealed partial class ZLibCompressionOptions
{
public ZLibCompressionOptions() { }
public int CompressionLevel { get { throw null; } set { } }
public System.IO.Compression.ZLibCompressionStrategy CompressionStrategy { get { throw null; } set { } }
+ public int WindowLog { get { throw null; } set { } }
}
}namespace System.IO.Compression
{
public sealed partial class DeflateDecoder : System.IDisposable
{
public DeflateDecoder() { }
public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
public void Dispose() { }
public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class DeflateEncoder : System.IDisposable
{
public DeflateEncoder() { }
public DeflateEncoder(int quality) { }
public DeflateEncoder(int quality, int windowLog) { }
public DeflateEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
public void Dispose() { }
public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
public static long GetMaxCompressedLength(long inputLength) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality, int windowLog) { throw null; }
}
public sealed partial class GZipDecoder : System.IDisposable
{
public GZipDecoder() { }
public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
public void Dispose() { }
public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class GZipEncoder : System.IDisposable
{
public GZipEncoder() { }
public GZipEncoder(int quality) { }
public GZipEncoder(int quality, int windowLog) { }
public GZipEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
public void Dispose() { }
public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
public static long GetMaxCompressedLength(long inputLength) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality, int windowLog) { throw null; }
}
public sealed partial class ZLibDecoder : System.IDisposable
{
public ZLibDecoder() { }
public System.Buffers.OperationStatus Decompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten) { throw null; }
public void Dispose() { }
public static bool TryDecompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
}
public sealed partial class ZLibEncoder : System.IDisposable
{
public ZLibEncoder() { }
public ZLibEncoder(int quality) { }
public ZLibEncoder(int quality, int windowLog) { }
public ZLibEncoder(System.IO.Compression.ZLibCompressionOptions options) { }
public System.Buffers.OperationStatus Compress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesConsumed, out int bytesWritten, bool isFinalBlock) { throw null; }
public void Dispose() { }
public System.Buffers.OperationStatus Flush(System.Span<byte> destination, out int bytesWritten) { throw null; }
public static long GetMaxCompressedLength(long inputLength) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality) { throw null; }
public static bool TryCompress(System.ReadOnlySpan<byte> source, System.Span<byte> destination, out int bytesWritten, int quality, int windowLog) { throw null; }
}
}API Usage ( updated by @iremyux )
// Simple one-shot compression (Deflate)
byte[] compressed = new byte[DeflateEncoder.GetMaxCompressedLength(data.Length)];
bool success = DeflateEncoder.TryCompress(data, compressed, out int bytesWritten);
// With compression level (ZLib format)
ZLibEncoder.TryCompress(data, compressed, out bytesWritten, CompressionLevel.SmallestSize);
// GZip format
GZipEncoder.TryCompress(data, compressed, out bytesWritten, CompressionLevel.Fastest);
// Stateful for streaming/chunked data
using var encoder = new DeflateEncoder(CompressionLevel.Optimal);
encoder.Compress(chunk1, dest, out int consumed, out int written, isFinalBlock: false);
encoder.Compress(chunk2, dest, out consumed, out written, isFinalBlock: true);
// With strategy
using var encoder2 = new ZLibEncoder(CompressionLevel.Fastest, ZLibCompressionStrategy.HuffmanOnly);
// With fine-grained options (compression level 0-9)
var options = new ZLibCompressionOptions { CompressionLevel = 9, CompressionStrategy = ZLibCompressionStrategy.Filtered };
using var encoder3 = new GZipEncoder(options);
// Decompression
using var decoder = new GZipDecoder();
decoder.Decompress(compressed, decompressed, out consumed, out written);
// One-shot decompression
ZLibDecoder.TryDecompress(compressed, decompressed, out bytesWritten);Alternative Designs ( updated by @iremyux )
- We considered having a single
ZlibEncoderclass with aZlibCompressionFormatenum parameter (Deflate, ZLib, GZip) to select the output format. However, this would be confusing for users: creating a GZip formatted output from something called "ZlibEncoder" is unintuitive. It also doesn't match the existing pattern where DeflateStream, GZipStream, and ZLibStream are separate classes.